Merge branch 'main' into main

This commit is contained in:
Alfred Berg 2024-09-06 20:35:21 +02:00 committed by GitHub
commit 6294b10983
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 4190 additions and 266 deletions

View file

@ -309,11 +309,33 @@ The following command will enumerate deleted and hidden commits on a GitHub repo
trufflehog github-experimental --repo https://github.com/<USER>/<REPO>.git --object-discovery
```
In addition to the normal TruffleHog output, the `--object-discovery` flag creates two files in a new `$HOME/.trufflehog` directory: `valid_hidden.txt` and `invalid.txt`. These are used to track state during commit enumeration, as well as to provide users with a complete list of all hidden and deleted commits (`valid_hidden.txt`). If you'd like to automatically remove these files after scanning, please add the flag `--delete-cached-data`.
In addition to the normal TruffleHog output, the `--object-discovery` flag creates two files in a new `$HOME/.trufflehog` directory: `valid_hidden.txt` and `invalid.txt`. These are used to track state during commit enumeration, as well as to provide users with a complete list of all hidden and deleted commits (`valid_hidden.txt`). If you'd like to automatically remove these files after scanning, please add the flag `--delete-cached-data`.
**Note**: Enumerating all valid commits on a repository using this method takes between 20 minutes and a few hours, depending on the size of your repository. We added a progress bar to keep you updated on how long the enumeration will take. The actual secret scanning runs extremely fast.
For more information on Cross Fork Object References, please [read our blog post](https://trufflesecurity.com/blog/anyone-can-access-deleted-and-private-repo-data-github).
For more information on Cross Fork Object References, please [read our blog post](https://trufflesecurity.com/blog/anyone-can-access-deleted-and-private-repo-data-github).
## 16. Scan Hugging Face
### Scan a Hugging Face Model, Dataset or Space
```bash
trufflehog huggingface --model <model_id> --space <space_id> --dataset <dataset_id>
```
### Scan all Models, Datasets and Spaces belonging to a Hugging Face Organization or User
```bash
trufflehog huggingface --org <orgname> --user <username>
```
(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>`.
### Scan Discussion and PR Comments
```bash
trufflehog huggingface --model <model_id> --include-discussions --include-prs
```
# :question: FAQ

2
go.mod
View file

@ -141,7 +141,6 @@ require (
github.com/DataDog/zstd v1.5.5 // indirect
github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.11.5 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/STARRY-S/zip v0.1.0 // indirect
github.com/alecthomas/chroma/v2 v2.8.0 // indirect
@ -165,7 +164,6 @@ require (
github.com/charmbracelet/x/windows v0.1.0 // indirect
github.com/cloudflare/circl v1.3.8 // indirect
github.com/containerd/containerd v1.7.18 // indirect
github.com/containerd/errdefs v0.1.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect

104
go.sum
View file

@ -7,20 +7,10 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ=
cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc=
cloud.google.com/go/auth v0.7.3 h1:98Vr+5jMaCZ5NZk6e/uBgf60phTk/XN84r8QEWB9yjY=
cloud.google.com/go/auth v0.7.3/go.mod h1:HJtWUx1P5eqjy/f6Iq5KeytNpbAcGolPhOgyop2LlzA=
cloud.google.com/go/auth v0.8.0 h1:y8jUJLl/Fg+qNBWxP/Hox2ezJvjkrPb952PC1p0G6A4=
cloud.google.com/go/auth v0.8.0/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo=
cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc=
cloud.google.com/go/auth v0.9.0 h1:cYhKl1JUhynmxjXfrk4qdPc6Amw7i+GC9VLflgT0p5M=
cloud.google.com/go/auth v0.9.0/go.mod h1:2HsApZBr9zGZhC9QAXsYVYaWk8kNUt37uny+XVKi7wM=
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY=
cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
@ -28,18 +18,12 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/iam v1.1.12 h1:JixGLimRrNGcxvJEQ8+clfLxPlbeZA6MuRJ+qJNQ5Xw=
cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg=
cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4=
cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus=
cloud.google.com/go/longrunning v0.5.11 h1:Havn1kGjz3whCfoD8dxMLP73Ph5w+ODyZB9RUsDxtGk=
cloud.google.com/go/longrunning v0.5.11/go.mod h1:rDn7//lmlfWV1Dx6IB4RatCPenTwwmqXuiP0/RgoEO4=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/secretmanager v1.13.5 h1:tXlHvpm97mFD0Lv50N4U4zlXfkoTNay3BmpNA/W7/oI=
cloud.google.com/go/secretmanager v1.13.5/go.mod h1:/OeZ88l5Z6nBVilV0SXgv6XJ243KP2aIhSWRMrbvDCQ=
cloud.google.com/go/secretmanager v1.13.6 h1:0ZEl/LuoB4xQsjVfQt3Gi/dZfOv36n4JmdPrMargzYs=
cloud.google.com/go/secretmanager v1.13.6/go.mod h1:x2ySyOrqv3WGFRFn2Xk10iHmNmvmcEVSSqc30eb1bhw=
cloud.google.com/go/secretmanager v1.14.0 h1:P2RRu2NEsQyOjplhUPvWKqzDXUKzwejHLuSUBHI8c4w=
cloud.google.com/go/secretmanager v1.14.0/go.mod h1:q0hSFHzoW7eRgyYFH8trqEFavgrMeiJI4FETNN78vhM=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
@ -105,8 +89,6 @@ github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.11.5 h1:haEcLNpj9Ka1gd3B3tAEs9CpE0c+1IhoL59w/exYU38=
github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/STARRY-S/zip v0.1.0 h1:eUER3jKmHKXjv+iy3BekLa+QnNSo1Lqz4eTzYBcGDqo=
@ -170,22 +152,16 @@ github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
github.com/charmbracelet/bubbletea v0.26.6 h1:zTCWSuST+3yZYZnVSvbXwKOPRSNZceVeqpzOLN2zq1s=
github.com/charmbracelet/bubbletea v0.26.6/go.mod h1:dz8CWPlfCCGLFbBlTY4N7bjLiyOGDJEnd2Muu7pOWhk=
github.com/charmbracelet/bubbletea v0.27.0 h1:Mznj+vvYuYagD9Pn2mY7fuelGvP0HAXtZYGgRBCbHvU=
github.com/charmbracelet/bubbletea v0.27.0/go.mod h1:5MdP9XH6MbQkgGhnlxUqCNmBXf9I74KRQ8HIidRxV1Y=
github.com/charmbracelet/glamour v0.7.0 h1:2BtKGZ4iVJCDfMF229EzbeR1QRKLWztO9dMtjmqZSng=
github.com/charmbracelet/glamour v0.7.0/go.mod h1:jUMh5MeihljJPQbJ/wf4ldw2+yBP59+ctV36jASy7ps=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY=
github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM=
github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ=
@ -210,8 +186,6 @@ github.com/cloudflare/circl v1.3.8/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZ
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/containerd v1.7.18 h1:jqjZTQNfXGoEaZdW1WwPU0RqSn1Bm2Ay/KJPUuO8nao=
github.com/containerd/containerd v1.7.18/go.mod h1:IYEk9/IO6wAPUz2bCMVUbsfXjzw5UNP5fLz4PsUygQ4=
github.com/containerd/errdefs v0.1.0 h1:m0wCRBiu1WJT/Fr+iOoQHMQS/eP5myQ8lCv4Dz5ZURM=
github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
@ -250,14 +224,10 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM=
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE=
github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v27.0.3+incompatible h1:aBGI9TeQ4MPlhquTQKq9XbK79rKFVwXNUAYz9aXyEBE=
github.com/docker/docker v27.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
@ -285,8 +255,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
@ -374,8 +342,8 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4=
github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -416,8 +384,6 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0=
github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI=
github.com/google/go-containerregistry v0.20.2 h1:B1wPJ1SN/S7pB+ZAimcciVD+r+yV/l/DSArMxlbwseo=
github.com/google/go-containerregistry v0.20.2/go.mod h1:z38EKdKh4h7IP2gSfUUqEvalZBqs6AoLeWfUy34nQC8=
github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4=
@ -667,21 +633,13 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_golang v1.20.1 h1:IMJXHOD6eARkQpxo8KkhgEVFlBNm+nkrFUyGlIu7Na8=
github.com/prometheus/client_golang v1.20.1/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
@ -703,8 +661,6 @@ github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXI
github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0=
github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.14.0+incompatible h1:KDSasSTktAqMJCYClHVE94Fcif2i7P7wzISv1sU6DUA=
github.com/sendgrid/sendgrid-go v3.14.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sendgrid/sendgrid-go v3.15.0+incompatible h1:oB6ujJD2aFcQRjmZLmmXiiUF9CBYKzsvYdPAS/71cSU=
github.com/sendgrid/sendgrid-go v3.15.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
@ -752,28 +708,16 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tailscale/depaware v0.0.0-20240804103531-585336c3e1b3 h1:1wRadBXZ4ddC71FYpzDTS3weuj0IOgmeptZtgTDGU7g=
github.com/tailscale/depaware v0.0.0-20240804103531-585336c3e1b3/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME=
github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E=
github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5UMw/kRiISng037Gxdw=
github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8=
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.32.0 h1:nY9P1UKsKWoGuL01dbidgVupahMA3uIa3KbCaZgqsl4=
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.32.0/go.mod h1:DcS1GwqZR18taQGusSQrW/nsIZOlgJzV2pSnlkcXBSY=
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.33.0 h1:tVsooNzk7SgYDO1OnqeIgihDYiD/vSBNBqwqCfauIJY=
github.com/testcontainers/testcontainers-go/modules/elasticsearch v0.33.0/go.mod h1:qmspvRf+Hx0iyqKQUmTg1jTNiO7HHGNrx98t/HksVfg=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.32.0 h1:DvmvHV1irfNIVBhixeTAcoaWCvmdkoNQxRmZisqic4E=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.32.0/go.mod h1:z0ZvM2V2iThZGrzEN6sddJpvnGhJd6O1O0FTFoZXmpk=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0 h1:iXVA84s5hKMS5gn01GWOYHE3ymy/2b+0YkpFeTxB2XY=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.33.0/go.mod h1:R6tMjTojRiaoo89fh/hf7tOmfzohdqSU17R9DwSVSog=
github.com/testcontainers/testcontainers-go/modules/mssql v0.32.0 h1:ZE22/n2X4bLUI3038ov1bcWSsluFIKQpjFYVU9E5tW8=
github.com/testcontainers/testcontainers-go/modules/mssql v0.32.0/go.mod h1:1zk90NOHq3FZo2m4JFB3B2LrcVB2MNjgYjjL4o8XbBQ=
github.com/testcontainers/testcontainers-go/modules/mssql v0.33.0 h1:gD4pHUPnEm5Bwup8KFdVmwXJLpyVy1hsp6bOXHAUlTA=
github.com/testcontainers/testcontainers-go/modules/mssql v0.33.0/go.mod h1:HdgR2Q9SsGqohT6nhtU3tnG56iNGUV1Tr5If0QypZl0=
github.com/testcontainers/testcontainers-go/modules/mysql v0.32.0 h1:6vjJOVJSWDTyNvQmB8EFTmv20ScquRWZa+pM1hZNodc=
github.com/testcontainers/testcontainers-go/modules/mysql v0.32.0/go.mod h1:Q91G1jl4fSl75OICi+Bb6BQeU7LpKZaSfKvHOXRwPyI=
github.com/testcontainers/testcontainers-go/modules/mysql v0.33.0 h1:1JN7YEEepTMJmGI2hW678IiiYoLM5HDp3vbCPmUokJ8=
github.com/testcontainers/testcontainers-go/modules/mysql v0.33.0/go.mod h1:9tZZwRW5s3RaI5X0Wnc+GXNJFXqbkKmob2nBHbfA/5E=
github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0 h1:ZE4dTdswj3P0j71nL+pL0m2e5HTXJwPoIFr+DDgdPaU=
github.com/testcontainers/testcontainers-go/modules/postgres v0.32.0/go.mod h1:njrNuyuoF2fjhVk6TG/R3Oeu82YwfYkbf5WVTyBXhV4=
github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0 h1:c+Gt+XLJjqFAejgX4hSpnHIpC9eAhvgI/TFWL/PbrFI=
github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0/go.mod h1:I4DazHBoWDyf69ByOIyt3OdNjefiUx372459txOpQ3o=
github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc=
@ -839,8 +783,6 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
go.einride.tech/aip v0.60.0 h1:h6bgabZ5BCfAptbGex8jbh3VvPBRLa6xq+pQ1CAjHYw=
go.einride.tech/aip v0.60.0/go.mod h1:SdLbSbgSU60Xkb4TMkmsZEQPHeEWx0ikBoq5QnqZvdg=
go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
go.mongodb.org/mongo-driver v1.16.1 h1:rIVLL3q0IHM39dvE+z2ulZLp9ENZKThVfuvN/IiN4l8=
go.mongodb.org/mongo-driver v1.16.1/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -849,30 +791,20 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
@ -907,8 +839,6 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -973,8 +903,6 @@ golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -1042,10 +970,7 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -1057,7 +982,6 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
@ -1074,14 +998,10 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1133,12 +1053,6 @@ google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.190.0 h1:ASM+IhLY1zljNdLu19W1jTmU6A+gMk6M46Wlur61s+Q=
google.golang.org/api v0.190.0/go.mod h1:QIr6I9iedBLnfqoD6L6Vze1UvS5Hzj5r2aUBOaZnLHo=
google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk=
google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E=
google.golang.org/api v0.192.0 h1:PljqpNAfZaaSpS+TnANfnNAXKdzHM/B9bKhwRlo7JP0=
google.golang.org/api v0.192.0/go.mod h1:9VcphjvAxPKLmSxVSzPlSRXy/5ARMEw5bf58WoVXafQ=
google.golang.org/api v0.193.0 h1:eOGDoJFsLU+HpCBaDJex2fWiYujAw9KbXgpOAMePoUs=
google.golang.org/api v0.193.0/go.mod h1:Po3YMV1XZx+mTku3cfJrlIYR03wiGrCOsdpC67hjZvw=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -1161,18 +1075,10 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf h1:OqdXDEakZCVtDiZTjcxfwbHPCT11ycCEsTKesBVKvyY=
google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M=
google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142 h1:oLiyxGgE+rt22duwci1+TG7bg2/L1LQsXwfjPlmuJA0=
google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142/go.mod h1:G11eXq53iI5Q+kyNOmCvnzBaxEA2Q/Ik5Tj7nqBE8j4=
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f h1:b1Ln/PG8orm0SsBbHZWke8dDp2lrCD4jSmfglFpTZbk=
google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys=
google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf h1:GillM0Ef0pkZPIB+5iO6SDK+4T9pf6TpaYR6ICD5rVE=
google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -1185,8 +1091,6 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

View file

@ -1,8 +1,9 @@
//go:generate generate_permissions permissions.yaml permissions.go bitbucket
package bitbucket
import (
"encoding/json"
"fmt"
"errors"
"net/http"
"os"
"sort"
@ -18,37 +19,33 @@ import (
var _ analyzers.Analyzer = (*Analyzer)(nil)
type Analyzer struct {
Cfg *config.Config
}
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Bitbucket }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
_, err := AnalyzePermissions(a.Cfg, credInfo["key"])
if err != nil {
return nil, err
}
return nil, fmt.Errorf("not implemented")
var resource_name_map = map[string]string{
"repo_access_token": "Repository",
"project_access_token": "Project",
"workspace_access_token": "Workspace",
}
type SecretInfo struct {
Type string
OauthScopes []analyzers.Permission
OauthScopes []string
Repos []Repo
}
type Repo struct {
ID string `json:"uuid"`
FullName string `json:"full_name"`
RepoName string `json:"name"`
Project struct {
ID string `json:"uuid"`
Name string `json:"name"`
} `json:"project"`
Workspace struct {
ID string `json:"uuid"`
Name string `json:"name"`
} `json:"workspace"`
IsPrivate bool `json:"is_private"`
Owner struct {
ID string `json:"uuid"`
Username string `json:"username"`
} `json:"owner"`
Role string
@ -58,7 +55,81 @@ type RepoJSON struct {
Values []Repo `json:"values"`
}
func getScopesAndType(cfg *config.Config, key string) (string, []analyzers.Permission, error) {
type Analyzer struct {
Cfg *config.Config
}
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Bitbucket }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
key, ok := credInfo["key"]
if !ok {
return nil, errors.New("key not found in credentialInfo")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return secretInfoToAnalyzerResult(info), nil
}
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzerpb.AnalyzerType_Bitbucket,
}
// add unbounded resources
result.UnboundedResources = make([]analyzers.Resource, len(info.Repos))
for i, repo := range info.Repos {
result.UnboundedResources[i] = analyzers.Resource{
Type: "repository",
Name: repo.FullName,
FullyQualifiedName: "bitbucket.com/repository/" + repo.ID,
Parent: &analyzers.Resource{
Type: "project",
Name: repo.Project.Name,
FullyQualifiedName: "bitbucket.com/project/" + repo.Project.ID,
Parent: &analyzers.Resource{
Type: "workspace",
Name: repo.Workspace.Name,
FullyQualifiedName: "bitbucket.com/workspace/" + repo.Workspace.ID,
},
},
Metadata: map[string]any{
"owner_id": repo.Owner.ID,
"owner": repo.Owner.Username,
"is_private": repo.IsPrivate,
"role": repo.Role,
},
}
}
credentialResource := &analyzers.Resource{
Type: info.Type,
Name: resource_name_map[info.Type],
FullyQualifiedName: "bitbucket.com/credential/" + info.Type,
Metadata: map[string]any{
"type": credential_type_map[info.Type],
},
}
for _, scope := range info.OauthScopes {
result.Bindings = append(result.Bindings, analyzers.Binding{
Resource: *credentialResource,
Permission: analyzers.Permission{
Value: scope,
},
})
}
return &result
}
func getScopesAndType(cfg *config.Config, key string) (string, []string, error) {
// client
client := analyzers.NewAnalyzeClient(cfg)
@ -82,10 +153,7 @@ func getScopesAndType(cfg *config.Config, key string) (string, []analyzers.Permi
credentialType := resp.Header.Get("x-credential-type")
oauthScopes := resp.Header.Get("x-oauth-scopes")
var scopes []analyzers.Permission
for _, scope := range strings.Split(oauthScopes, ", ") {
scopes = append(scopes, analyzers.Permission{Value: scope})
}
scopes := strings.Split(oauthScopes, ", ")
return credentialType, scopes, nil
}
@ -183,13 +251,21 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
}, nil
}
func convertScopeToAnalyzerPermissions(scopes []string) []analyzers.Permission {
permissions := make([]analyzers.Permission, len(scopes))
for _, scope := range scopes {
permissions = append(permissions, analyzers.Permission{Value: scope})
}
return permissions
}
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
info, err := AnalyzePermissions(cfg, key)
if err != nil {
color.Red("[x] Error: %s", err.Error())
return
}
printScopes(info.Type, info.OauthScopes)
printScopes(info.Type, convertScopeToAnalyzerPermissions(info.OauthScopes))
printAccessibleRepositories(info.Repos)
}

View file

@ -0,0 +1,96 @@
package bitbucket
import (
_ "embed"
"encoding/json"
"sort"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
//go:embed expected_output.json
var expectedOutput []byte
func TestAnalyzer_Analyze(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
tests := []struct {
name string
sid string
key string
want string // JSON string
wantErr bool
}{
{
name: "valid Bitbucket key",
key: testSecrets.MustGetField("BITBUCKET_ANALYZE_TOKEN"),
want: string(expectedOutput),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := Analyzer{}
got, err := a.Analyze(ctx, map[string]string{"key": tt.key, "sid": tt.sid})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}
// bindings need to be in the same order to be comparable
sortBindings(got.Bindings)
// Marshal the actual result to JSON
gotJSON, err := json.Marshal(got)
if err != nil {
t.Fatalf("could not marshal got to JSON: %s", err)
}
// Parse the expected JSON string
var wantObj analyzers.AnalyzerResult
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
t.Fatalf("could not unmarshal want JSON string: %s", err)
}
// bindings need to be in the same order to be comparable
sortBindings(wantObj.Bindings)
// Marshal the expected result to JSON (to normalize)
wantJSON, err := json.Marshal(wantObj)
if err != nil {
t.Fatalf("could not marshal want to JSON: %s", err)
}
// Compare the JSON strings
if string(gotJSON) != string(wantJSON) {
// Pretty-print both JSON strings for easier comparison
var gotIndented []byte
gotIndented, err = json.MarshalIndent(got, "", " ")
if err != nil {
t.Fatalf("could not marshal got to indented JSON: %s", err)
}
t.Errorf("Analyzer.Analyze() = \n%s", gotIndented)
}
})
}
}
// Helper function to sort bindings
func sortBindings(bindings []analyzers.Binding) {
sort.SliceStable(bindings, func(i, j int) bool {
if bindings[i].Resource.Name == bindings[j].Resource.Name {
return bindings[i].Permission.Value < bindings[j].Permission.Value
}
return bindings[i].Resource.Name < bindings[j].Resource.Name
})
}

View file

@ -0,0 +1,92 @@
{
"AnalyzerType": 3,
"Bindings": [
{
"Resource": {
"Name": "Repository",
"FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
"Type": "repo_access_token",
"Metadata": {
"type": "Repository Access Token (Can access 1 repository)"
},
"Parent": null
},
"Permission": {
"Value": "pipeline",
"Parent": null
}
},
{
"Resource": {
"Name": "Repository",
"FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
"Type": "repo_access_token",
"Metadata": {
"type": "Repository Access Token (Can access 1 repository)"
},
"Parent": null
},
"Permission": {
"Value": "pullrequest",
"Parent": null
}
},
{
"Resource": {
"Name": "Repository",
"FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
"Type": "repo_access_token",
"Metadata": {
"type": "Repository Access Token (Can access 1 repository)"
},
"Parent": null
},
"Permission": {
"Value": "runner",
"Parent": null
}
},
{
"Resource": {
"Name": "Repository",
"FullyQualifiedName": "bitbucket.com/credential/repo_access_token",
"Type": "repo_access_token",
"Metadata": {
"type": "Repository Access Token (Can access 1 repository)"
},
"Parent": null
},
"Permission": {
"Value": "webhook",
"Parent": null
}
}
],
"UnboundedResources": [
{
"Name": "basit-trufflesec/repo1",
"FullyQualifiedName": "bitbucket.com/repository/{8961ef70-000c-47ca-9348-5f9ecee875d6}",
"Type": "repository",
"Metadata": {
"is_private": true,
"owner": "basit-trufflesec",
"owner_id": "{521b49b6-7709-484a-8aa8-ecc3a6da08eb}",
"role": "admin"
},
"Parent": {
"Name": "repo-analyzer",
"FullyQualifiedName": "bitbucket.com/project/{8a693e10-087f-41fc-ba67-2d1414ab1c86}",
"Type": "project",
"Metadata": null,
"Parent": {
"Name": "basit-trufflesec",
"FullyQualifiedName": "bitbucket.com/workspace/{521b49b6-7709-484a-8aa8-ecc3a6da08eb}",
"Type": "workspace",
"Metadata": null,
"Parent": null
}
}
}
],
"Metadata": null
}

View file

@ -0,0 +1,131 @@
// Code generated by go generate; DO NOT EDIT.
package bitbucket
import "errors"
type Permission int
const (
Invalid Permission = iota
Project Permission = iota
ProjectAdmin Permission = iota
Repository Permission = iota
RepositoryWrite Permission = iota
RepositoryAdmin Permission = iota
RepositoryDelete Permission = iota
Pullrequest Permission = iota
PullrequestWrite Permission = iota
Webhook Permission = iota
Account Permission = iota
Pipeline Permission = iota
PipelineWrite Permission = iota
PipelineVariable Permission = iota
Runner Permission = iota
RunnerWrite Permission = iota
)
var (
PermissionStrings = map[Permission]string{
Project: "project",
ProjectAdmin: "project:admin",
Repository: "repository",
RepositoryWrite: "repository:write",
RepositoryAdmin: "repository:admin",
RepositoryDelete: "repository:delete",
Pullrequest: "pullrequest",
PullrequestWrite: "pullrequest:write",
Webhook: "webhook",
Account: "account",
Pipeline: "pipeline",
PipelineWrite: "pipeline:write",
PipelineVariable: "pipeline:variable",
Runner: "runner",
RunnerWrite: "runner:write",
}
StringToPermission = map[string]Permission{
"project": Project,
"project:admin": ProjectAdmin,
"repository": Repository,
"repository:write": RepositoryWrite,
"repository:admin": RepositoryAdmin,
"repository:delete": RepositoryDelete,
"pullrequest": Pullrequest,
"pullrequest:write": PullrequestWrite,
"webhook": Webhook,
"account": Account,
"pipeline": Pipeline,
"pipeline:write": PipelineWrite,
"pipeline:variable": PipelineVariable,
"runner": Runner,
"runner:write": RunnerWrite,
}
PermissionIDs = map[Permission]int{
Project: 1,
ProjectAdmin: 2,
Repository: 3,
RepositoryWrite: 4,
RepositoryAdmin: 5,
RepositoryDelete: 6,
Pullrequest: 7,
PullrequestWrite: 8,
Webhook: 9,
Account: 10,
Pipeline: 11,
PipelineWrite: 12,
PipelineVariable: 13,
Runner: 14,
RunnerWrite: 15,
}
IdToPermission = map[int]Permission{
1: Project,
2: ProjectAdmin,
3: Repository,
4: RepositoryWrite,
5: RepositoryAdmin,
6: RepositoryDelete,
7: Pullrequest,
8: PullrequestWrite,
9: Webhook,
10: Account,
11: Pipeline,
12: PipelineWrite,
13: PipelineVariable,
14: Runner,
15: RunnerWrite,
}
)
// ToString converts a Permission enum to its string representation
func (p Permission) ToString() (string, error) {
if str, ok := PermissionStrings[p]; ok {
return str, nil
}
return "", errors.New("invalid permission")
}
// ToID converts a Permission enum to its ID
func (p Permission) ToID() (int, error) {
if id, ok := PermissionIDs[p]; ok {
return id, nil
}
return 0, errors.New("invalid permission")
}
// PermissionFromString converts a string representation to its Permission enum
func PermissionFromString(s string) (Permission, error) {
if p, ok := StringToPermission[s]; ok {
return p, nil
}
return 0, errors.New("invalid permission string")
}
// PermissionFromID converts an ID to its Permission enum
func PermissionFromID(id int) (Permission, error) {
if p, ok := IdToPermission[id]; ok {
return p, nil
}
return 0, errors.New("invalid permission ID")
}

View file

@ -0,0 +1,16 @@
permissions:
- project
- project:admin
- repository
- repository:write
- repository:admin
- repository:delete
- pullrequest
- pullrequest:write
- webhook
- account
- pipeline
- pipeline:write
- pipeline:variable
- runner
- runner:write

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,9 @@
//go:generate generate_permissions permissions.yaml permissions.go mailchimp
package mailchimp
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
@ -26,11 +28,66 @@ type Analyzer struct {
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Mailchimp }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
_, err := AnalyzePermissions(a.Cfg, credInfo["key"])
key, ok := credInfo["key"]
if !ok {
return nil, errors.New("key not found in credentialInfo")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return nil, fmt.Errorf("not implemented")
return secretInfoToAnalyzerResult(info), nil
}
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzerpb.AnalyzerType_Mailchimp,
Bindings: make([]analyzers.Binding, 0, len(StringToPermission)),
UnboundedResources: make([]analyzers.Resource, 0, len(info.Domains.Domains)),
}
accountResource := analyzers.Resource{
Name: info.Metadata.AccountName,
FullyQualifiedName: "mailchimp.com/account/" + info.Metadata.AccountID,
Type: "account",
Metadata: map[string]any{
"email": info.Metadata.Email,
"role": info.Metadata.Role,
"member_since": info.Metadata.MemberSince,
"pricing_plan": info.Metadata.PricingPlan,
"account_timezone": info.Metadata.AccountTimezone,
"last_login": info.Metadata.LastLogin,
"total_subscribers": info.Metadata.TotalSubscribers,
},
}
for perm := range StringToPermission {
result.Bindings = append(result.Bindings, analyzers.Binding{
Resource: accountResource,
Permission: analyzers.Permission{
Value: perm,
},
})
}
for _, domain := range info.Domains.Domains {
result.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{
Name: domain.Domain,
FullyQualifiedName: "mailchimp.com/domain/" + domain.Domain,
Type: "domain",
Metadata: map[string]any{
"verified": domain.Verified,
"authenticated": domain.Authenticated,
},
Parent: &accountResource,
})
}
return &result
}
type MetadataJSON struct {

View file

@ -0,0 +1,101 @@
package mailchimp
import (
_ "embed"
"encoding/json"
"sort"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
//go:embed expected_output.json
var expected_output []byte
func TestAnalyzer_Analyze(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors2")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
tests := []struct {
name string
key string
want string // JSON string
wantErr bool
}{
{
name: "valid Mailchimp key",
key: testSecrets.MustGetField("MAILCHIMP"),
want: string(expected_output),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := Analyzer{Cfg: &config.Config{}}
got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}
// Bindings need to be in the same order to be comparable
sortBindings(got.Bindings)
// Marshal the actual result to JSON
gotJSON, err := json.Marshal(got)
if err != nil {
t.Fatalf("could not marshal got to JSON: %s", err)
}
// Parse the expected JSON string
var wantObj analyzers.AnalyzerResult
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
t.Fatalf("could not unmarshal want JSON string: %s", err)
}
// Bindings need to be in the same order to be comparable
sortBindings(wantObj.Bindings)
// Marshal the expected result to JSON (to normalize)
wantJSON, err := json.Marshal(wantObj)
if err != nil {
t.Fatalf("could not marshal want to JSON: %s", err)
}
// Compare the JSON strings
if string(gotJSON) != string(wantJSON) {
// Pretty-print both JSON strings for easier comparison
var gotIndented, wantIndented []byte
gotIndented, err = json.Marshal(got)
// gotIndented, err = json.MarshalIndent(got, "", " ")
if err != nil {
t.Fatalf("could not marshal got to indented JSON: %s", err)
}
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
if err != nil {
t.Fatalf("could not marshal want to indented JSON: %s", err)
}
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
}
})
}
}
// Helper function to sort bindings
func sortBindings(bindings []analyzers.Binding) {
sort.SliceStable(bindings, func(i, j int) bool {
if bindings[i].Resource.Name == bindings[j].Resource.Name {
return bindings[i].Permission.Value < bindings[j].Permission.Value
}
return bindings[i].Resource.Name < bindings[j].Resource.Name
})
}

View file

@ -0,0 +1,391 @@
// Code generated by go generate; DO NOT EDIT.
package mailchimp
import "errors"
type Permission int
const (
Invalid Permission = iota
InviteUsers Permission = iota
RevokeAccountAccess Permission = iota
SetUserAccessLevel Permission = iota
Require2FactorAuthentication Permission = iota
ChangeBillingInformation Permission = iota
ChangeCompanyOrganizationName Permission = iota
AddOrAccessApiKeys Permission = iota
CheckReconnectIntegrations Permission = iota
ReferralProgram Permission = iota
AccountExport Permission = iota
CloseAccount Permission = iota
AddFilesToContentStudio Permission = iota
OptInToReceiveEmailsFromMailchimp Permission = iota
CreateAudiences Permission = iota
ViewAudiences Permission = iota
AudienceExport Permission = iota
AudienceImport Permission = iota
AddContacts Permission = iota
DeleteContacts Permission = iota
ViewSegments Permission = iota
EditAudienceSettings Permission = iota
ArchiveContacts Permission = iota
CreateOrImportTemplates Permission = iota
EditTemplates Permission = iota
CreateEmails Permission = iota
EditEmails Permission = iota
SendPublishEmails Permission = iota
PauseUnpublishEmails Permission = iota
DeleteEmails Permission = iota
SubmitSmsMarketingApplication Permission = iota
CreateSendSmsMmsMessages Permission = iota
PurchaseSmsCredits Permission = iota
ViewEmailReports Permission = iota
ViewSmsReports Permission = iota
ViewAbuseReports Permission = iota
ViewEmailStatistics Permission = iota
UseConversations Permission = iota
ViewEmailRecipients Permission = iota
TopLocations Permission = iota
EmailContactDetails Permission = iota
EmailOpenDetails Permission = iota
ECommerceProductActivity Permission = iota
DomainPerformance Permission = iota
CreateYourWebsite Permission = iota
PublishUnpublishYourWebsite Permission = iota
ViewReport Permission = iota
CreateALandingPage Permission = iota
PublishUnpublishALandingPage Permission = iota
ReplicateALandingPage Permission = iota
VerifyADomain Permission = iota
ConnectADomain Permission = iota
CreateCustomerJourney Permission = iota
ViewCustomerJourney Permission = iota
EditCustomerJourney Permission = iota
TurnOnPauseTurnBackOn Permission = iota
ViewMessages Permission = iota
LeaveComments Permission = iota
SendMessages Permission = iota
ToggleUserNotifications Permission = iota
CreateSurvey Permission = iota
EditSurvey Permission = iota
PublishSurvey Permission = iota
DeleteSurvey Permission = iota
CreateForm Permission = iota
EditForm Permission = iota
PublishForm Permission = iota
DeleteForm Permission = iota
)
var (
PermissionStrings = map[Permission]string{
InviteUsers: "invite_users",
RevokeAccountAccess: "revoke_account_access",
SetUserAccessLevel: "set_user_access_level",
Require2FactorAuthentication: "require_2_factor_authentication",
ChangeBillingInformation: "change_billing_information",
ChangeCompanyOrganizationName: "change_company_organization_name",
AddOrAccessApiKeys: "add_or_access_api_keys",
CheckReconnectIntegrations: "check_reconnect_integrations",
ReferralProgram: "referral_program",
AccountExport: "account_export",
CloseAccount: "close_account",
AddFilesToContentStudio: "add_files_to_content_studio",
OptInToReceiveEmailsFromMailchimp: "opt_in_to_receive_emails_from_mailchimp",
CreateAudiences: "create_audiences",
ViewAudiences: "view_audiences",
AudienceExport: "audience_export",
AudienceImport: "audience_import",
AddContacts: "add_contacts",
DeleteContacts: "delete_contacts",
ViewSegments: "view_segments",
EditAudienceSettings: "edit_audience_settings",
ArchiveContacts: "archive_contacts",
CreateOrImportTemplates: "create_or_import_templates",
EditTemplates: "edit_templates",
CreateEmails: "create_emails",
EditEmails: "edit_emails",
SendPublishEmails: "send_publish_emails",
PauseUnpublishEmails: "pause_unpublish_emails",
DeleteEmails: "delete_emails",
SubmitSmsMarketingApplication: "submit_sms_marketing_application",
CreateSendSmsMmsMessages: "create_send_sms_mms_messages",
PurchaseSmsCredits: "purchase_sms_credits",
ViewEmailReports: "view_email_reports",
ViewSmsReports: "view_sms_reports",
ViewAbuseReports: "view_abuse_reports",
ViewEmailStatistics: "view_email_statistics",
UseConversations: "use_conversations",
ViewEmailRecipients: "view_email_recipients",
TopLocations: "top_locations",
EmailContactDetails: "email_contact_details",
EmailOpenDetails: "email_open_details",
ECommerceProductActivity: "e_commerce_product_activity",
DomainPerformance: "domain_performance",
CreateYourWebsite: "create_your_website",
PublishUnpublishYourWebsite: "publish_unpublish_your_website",
ViewReport: "view_report",
CreateALandingPage: "create_a_landing_page",
PublishUnpublishALandingPage: "publish_unpublish_a_landing_page",
ReplicateALandingPage: "replicate_a_landing_page",
VerifyADomain: "verify_a_domain",
ConnectADomain: "connect_a_domain",
CreateCustomerJourney: "create_customer_journey",
ViewCustomerJourney: "view_customer_journey",
EditCustomerJourney: "edit_customer_journey",
TurnOnPauseTurnBackOn: "turn_on_pause_turn_back_on",
ViewMessages: "view_messages",
LeaveComments: "leave_comments",
SendMessages: "send_messages",
ToggleUserNotifications: "toggle_user_notifications",
CreateSurvey: "create_survey",
EditSurvey: "edit_survey",
PublishSurvey: "publish_survey",
DeleteSurvey: "delete_survey",
CreateForm: "create_form",
EditForm: "edit_form",
PublishForm: "publish_form",
DeleteForm: "delete_form",
}
StringToPermission = map[string]Permission{
"invite_users": InviteUsers,
"revoke_account_access": RevokeAccountAccess,
"set_user_access_level": SetUserAccessLevel,
"require_2_factor_authentication": Require2FactorAuthentication,
"change_billing_information": ChangeBillingInformation,
"change_company_organization_name": ChangeCompanyOrganizationName,
"add_or_access_api_keys": AddOrAccessApiKeys,
"check_reconnect_integrations": CheckReconnectIntegrations,
"referral_program": ReferralProgram,
"account_export": AccountExport,
"close_account": CloseAccount,
"add_files_to_content_studio": AddFilesToContentStudio,
"opt_in_to_receive_emails_from_mailchimp": OptInToReceiveEmailsFromMailchimp,
"create_audiences": CreateAudiences,
"view_audiences": ViewAudiences,
"audience_export": AudienceExport,
"audience_import": AudienceImport,
"add_contacts": AddContacts,
"delete_contacts": DeleteContacts,
"view_segments": ViewSegments,
"edit_audience_settings": EditAudienceSettings,
"archive_contacts": ArchiveContacts,
"create_or_import_templates": CreateOrImportTemplates,
"edit_templates": EditTemplates,
"create_emails": CreateEmails,
"edit_emails": EditEmails,
"send_publish_emails": SendPublishEmails,
"pause_unpublish_emails": PauseUnpublishEmails,
"delete_emails": DeleteEmails,
"submit_sms_marketing_application": SubmitSmsMarketingApplication,
"create_send_sms_mms_messages": CreateSendSmsMmsMessages,
"purchase_sms_credits": PurchaseSmsCredits,
"view_email_reports": ViewEmailReports,
"view_sms_reports": ViewSmsReports,
"view_abuse_reports": ViewAbuseReports,
"view_email_statistics": ViewEmailStatistics,
"use_conversations": UseConversations,
"view_email_recipients": ViewEmailRecipients,
"top_locations": TopLocations,
"email_contact_details": EmailContactDetails,
"email_open_details": EmailOpenDetails,
"e_commerce_product_activity": ECommerceProductActivity,
"domain_performance": DomainPerformance,
"create_your_website": CreateYourWebsite,
"publish_unpublish_your_website": PublishUnpublishYourWebsite,
"view_report": ViewReport,
"create_a_landing_page": CreateALandingPage,
"publish_unpublish_a_landing_page": PublishUnpublishALandingPage,
"replicate_a_landing_page": ReplicateALandingPage,
"verify_a_domain": VerifyADomain,
"connect_a_domain": ConnectADomain,
"create_customer_journey": CreateCustomerJourney,
"view_customer_journey": ViewCustomerJourney,
"edit_customer_journey": EditCustomerJourney,
"turn_on_pause_turn_back_on": TurnOnPauseTurnBackOn,
"view_messages": ViewMessages,
"leave_comments": LeaveComments,
"send_messages": SendMessages,
"toggle_user_notifications": ToggleUserNotifications,
"create_survey": CreateSurvey,
"edit_survey": EditSurvey,
"publish_survey": PublishSurvey,
"delete_survey": DeleteSurvey,
"create_form": CreateForm,
"edit_form": EditForm,
"publish_form": PublishForm,
"delete_form": DeleteForm,
}
PermissionIDs = map[Permission]int{
InviteUsers: 1,
RevokeAccountAccess: 2,
SetUserAccessLevel: 3,
Require2FactorAuthentication: 4,
ChangeBillingInformation: 5,
ChangeCompanyOrganizationName: 6,
AddOrAccessApiKeys: 7,
CheckReconnectIntegrations: 8,
ReferralProgram: 9,
AccountExport: 10,
CloseAccount: 11,
AddFilesToContentStudio: 12,
OptInToReceiveEmailsFromMailchimp: 13,
CreateAudiences: 14,
ViewAudiences: 15,
AudienceExport: 16,
AudienceImport: 17,
AddContacts: 18,
DeleteContacts: 19,
ViewSegments: 20,
EditAudienceSettings: 21,
ArchiveContacts: 22,
CreateOrImportTemplates: 23,
EditTemplates: 24,
CreateEmails: 25,
EditEmails: 26,
SendPublishEmails: 27,
PauseUnpublishEmails: 28,
DeleteEmails: 29,
SubmitSmsMarketingApplication: 30,
CreateSendSmsMmsMessages: 31,
PurchaseSmsCredits: 32,
ViewEmailReports: 33,
ViewSmsReports: 34,
ViewAbuseReports: 35,
ViewEmailStatistics: 36,
UseConversations: 37,
ViewEmailRecipients: 38,
TopLocations: 39,
EmailContactDetails: 40,
EmailOpenDetails: 41,
ECommerceProductActivity: 42,
DomainPerformance: 43,
CreateYourWebsite: 44,
PublishUnpublishYourWebsite: 45,
ViewReport: 46,
CreateALandingPage: 47,
PublishUnpublishALandingPage: 48,
ReplicateALandingPage: 49,
VerifyADomain: 50,
ConnectADomain: 51,
CreateCustomerJourney: 52,
ViewCustomerJourney: 53,
EditCustomerJourney: 54,
TurnOnPauseTurnBackOn: 55,
ViewMessages: 56,
LeaveComments: 57,
SendMessages: 58,
ToggleUserNotifications: 59,
CreateSurvey: 60,
EditSurvey: 61,
PublishSurvey: 62,
DeleteSurvey: 63,
CreateForm: 64,
EditForm: 65,
PublishForm: 66,
DeleteForm: 67,
}
IdToPermission = map[int]Permission{
1: InviteUsers,
2: RevokeAccountAccess,
3: SetUserAccessLevel,
4: Require2FactorAuthentication,
5: ChangeBillingInformation,
6: ChangeCompanyOrganizationName,
7: AddOrAccessApiKeys,
8: CheckReconnectIntegrations,
9: ReferralProgram,
10: AccountExport,
11: CloseAccount,
12: AddFilesToContentStudio,
13: OptInToReceiveEmailsFromMailchimp,
14: CreateAudiences,
15: ViewAudiences,
16: AudienceExport,
17: AudienceImport,
18: AddContacts,
19: DeleteContacts,
20: ViewSegments,
21: EditAudienceSettings,
22: ArchiveContacts,
23: CreateOrImportTemplates,
24: EditTemplates,
25: CreateEmails,
26: EditEmails,
27: SendPublishEmails,
28: PauseUnpublishEmails,
29: DeleteEmails,
30: SubmitSmsMarketingApplication,
31: CreateSendSmsMmsMessages,
32: PurchaseSmsCredits,
33: ViewEmailReports,
34: ViewSmsReports,
35: ViewAbuseReports,
36: ViewEmailStatistics,
37: UseConversations,
38: ViewEmailRecipients,
39: TopLocations,
40: EmailContactDetails,
41: EmailOpenDetails,
42: ECommerceProductActivity,
43: DomainPerformance,
44: CreateYourWebsite,
45: PublishUnpublishYourWebsite,
46: ViewReport,
47: CreateALandingPage,
48: PublishUnpublishALandingPage,
49: ReplicateALandingPage,
50: VerifyADomain,
51: ConnectADomain,
52: CreateCustomerJourney,
53: ViewCustomerJourney,
54: EditCustomerJourney,
55: TurnOnPauseTurnBackOn,
56: ViewMessages,
57: LeaveComments,
58: SendMessages,
59: ToggleUserNotifications,
60: CreateSurvey,
61: EditSurvey,
62: PublishSurvey,
63: DeleteSurvey,
64: CreateForm,
65: EditForm,
66: PublishForm,
67: DeleteForm,
}
)
// ToString converts a Permission enum to its string representation
func (p Permission) ToString() (string, error) {
if str, ok := PermissionStrings[p]; ok {
return str, nil
}
return "", errors.New("invalid permission")
}
// ToID converts a Permission enum to its ID
func (p Permission) ToID() (int, error) {
if id, ok := PermissionIDs[p]; ok {
return id, nil
}
return 0, errors.New("invalid permission")
}
// PermissionFromString converts a string representation to its Permission enum
func PermissionFromString(s string) (Permission, error) {
if p, ok := StringToPermission[s]; ok {
return p, nil
}
return 0, errors.New("invalid permission string")
}
// PermissionFromID converts an ID to its Permission enum
func PermissionFromID(id int) (Permission, error) {
if p, ok := IdToPermission[id]; ok {
return p, nil
}
return 0, errors.New("invalid permission ID")
}

View file

@ -0,0 +1,68 @@
permissions:
- invite_users
- revoke_account_access
- set_user_access_level
- require_2_factor_authentication
- change_billing_information
- change_company_organization_name
- add_or_access_api_keys
- check_reconnect_integrations
- referral_program
- account_export
- close_account
- add_files_to_content_studio
- opt_in_to_receive_emails_from_mailchimp
- create_audiences
- view_audiences
- audience_export
- audience_import
- add_contacts
- delete_contacts
- view_segments
- edit_audience_settings
- archive_contacts
- create_or_import_templates
- edit_templates
- create_emails
- edit_emails
- send_publish_emails
- pause_unpublish_emails
- delete_emails
- submit_sms_marketing_application
- create_send_sms_mms_messages
- purchase_sms_credits
- view_email_reports
- view_sms_reports
- view_abuse_reports
- view_email_statistics
- use_conversations
- view_email_recipients
- top_locations
- email_contact_details
- email_open_details
- e_commerce_product_activity
- domain_performance
- create_your_website
- publish_unpublish_your_website
- view_report
- create_a_landing_page
- publish_unpublish_a_landing_page
- replicate_a_landing_page
- verify_a_domain
- connect_a_domain
- create_customer_journey
- view_customer_journey
- edit_customer_journey
- turn_on_pause_turn_back_on
- view_messages
- leave_comments
- send_messages
- toggle_user_notifications
- create_survey
- edit_survey
- publish_survey
- delete_survey
- create_form
- edit_form
- publish_form
- delete_form

View file

@ -1,3 +1,5 @@
//go:generate generate_permissions permissions.yaml permissions.go opsgenie
package opsgenie
import (
@ -14,8 +16,79 @@ import (
"github.com/jedib0t/go-pretty/table"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
var _ analyzers.Analyzer = (*Analyzer)(nil)
type Analyzer struct {
Cfg *config.Config
}
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Opsgenie }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
key, ok := credInfo["key"]
if !ok {
return nil, errors.New("missing key in credInfo")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return secretInfoToAnalyzerResult(info), nil
}
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzerpb.AnalyzerType_Opsgenie,
Metadata: nil,
Bindings: make([]analyzers.Binding, len(info.Permissions)),
UnboundedResources: make([]analyzers.Resource, len(info.Users)),
}
// Opsgenie has API integrations, so the key does not belong
// to a particular user or account, it itself is a resource
resource := analyzers.Resource{
Name: "Opsgenie API Integration Key",
FullyQualifiedName: "Opsgenie API Integration Key",
Type: "API Key",
Metadata: map[string]any{
"expires": "never",
},
}
for idx, permission := range info.Permissions {
result.Bindings[idx] = analyzers.Binding{
Resource: resource,
Permission: analyzers.Permission{
Value: permission,
},
}
}
// We can find list of users in the current account
// if the API key has Configuration Access, so these can be
// unbounded resources
for idx, user := range info.Users {
result.UnboundedResources[idx] = analyzers.Resource{
Name: user.FullName,
FullyQualifiedName: user.Username,
Type: "user",
Metadata: map[string]any{
"username": user.Username,
"role": user.Role.Name,
},
}
}
return &result
}
//go:embed scopes.json
var scopesConfig []byte
@ -195,7 +268,7 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
info.Permissions = permissions
if contains(permissions, "Configuration Access") {
if contains(permissions, "configuration_access") {
users, err := getUserList(cfg, key)
if err != nil {
return nil, fmt.Errorf("getting user list: %w", err)

View file

@ -0,0 +1,158 @@
package opsgenie
import (
"encoding/json"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
func TestAnalyzer_Analyze(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
key := testSecrets.MustGetField("OPSGENIE")
tests := []struct {
name string
key string
want string // JSON string
wantErr bool
}{
{
name: "valid Opsgenie API key",
key: key,
want: `{
"AnalyzerType": 11,
"Bindings": [
{
"Resource": {
"Name": "Opsgenie API Integration Key",
"FullyQualifiedName": "Opsgenie API Integration Key",
"Type": "API Key",
"Metadata": {
"expires": "never"
},
"Parent": null
},
"Permission": {
"Value": "configuration_access",
"Parent": null
}
},
{
"Resource": {
"Name": "Opsgenie API Integration Key",
"FullyQualifiedName": "Opsgenie API Integration Key",
"Type": "API Key",
"Metadata": {
"expires": "never"
},
"Parent": null
},
"Permission": {
"Value": "read",
"Parent": null
}
},
{
"Resource": {
"Name": "Opsgenie API Integration Key",
"FullyQualifiedName": "Opsgenie API Integration Key",
"Type": "API Key",
"Metadata": {
"expires": "never"
},
"Parent": null
},
"Permission": {
"Value": "delete",
"Parent": null
}
},
{
"Resource": {
"Name": "Opsgenie API Integration Key",
"FullyQualifiedName": "Opsgenie API Integration Key",
"Type": "API Key",
"Metadata": {
"expires": "never"
},
"Parent": null
},
"Permission": {
"Value": "create_and_update",
"Parent": null
}
}
],
"UnboundedResources": [
{
"Name": "John Scanner",
"FullyQualifiedName": "secretscanner02@zohomail.com",
"Type": "user",
"Metadata": {
"role": "Owner",
"username": "secretscanner02@zohomail.com"
},
"Parent": null
}
],
"Metadata": null
}`,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := Analyzer{Cfg: &config.Config{}}
got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}
// Marshal the actual result to JSON
gotJSON, err := json.Marshal(got)
if err != nil {
t.Fatalf("could not marshal got to JSON: %s", err)
}
// Parse the expected JSON string
var wantObj analyzers.AnalyzerResult
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
t.Fatalf("could not unmarshal want JSON string: %s", err)
}
// Marshal the expected result to JSON (to normalize)
wantJSON, err := json.Marshal(wantObj)
if err != nil {
t.Fatalf("could not marshal want to JSON: %s", err)
}
// Compare the JSON strings
if string(gotJSON) != string(wantJSON) {
// Pretty-print both JSON strings for easier comparison
var gotIndented, wantIndented []byte
gotIndented, err = json.MarshalIndent(got, "", " ")
if err != nil {
t.Fatalf("could not marshal got to indented JSON: %s", err)
}
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
if err != nil {
t.Fatalf("could not marshal want to indented JSON: %s", err)
}
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
}
})
}
}

View file

@ -0,0 +1,76 @@
// Code generated by go generate; DO NOT EDIT.
package opsgenie
import "errors"
type Permission int
const (
Invalid Permission = iota
ConfigurationAccess Permission = iota
Read Permission = iota
Delete Permission = iota
CreateAndUpdate Permission = iota
)
var (
PermissionStrings = map[Permission]string{
ConfigurationAccess: "configuration_access",
Read: "read",
Delete: "delete",
CreateAndUpdate: "create_and_update",
}
StringToPermission = map[string]Permission{
"configuration_access": ConfigurationAccess,
"read": Read,
"delete": Delete,
"create_and_update": CreateAndUpdate,
}
PermissionIDs = map[Permission]int{
ConfigurationAccess: 1,
Read: 2,
Delete: 3,
CreateAndUpdate: 4,
}
IdToPermission = map[int]Permission{
1: ConfigurationAccess,
2: Read,
3: Delete,
4: CreateAndUpdate,
}
)
// ToString converts a Permission enum to its string representation
func (p Permission) ToString() (string, error) {
if str, ok := PermissionStrings[p]; ok {
return str, nil
}
return "", errors.New("invalid permission")
}
// ToID converts a Permission enum to its ID
func (p Permission) ToID() (int, error) {
if id, ok := PermissionIDs[p]; ok {
return id, nil
}
return 0, errors.New("invalid permission")
}
// PermissionFromString converts a string representation to its Permission enum
func PermissionFromString(s string) (Permission, error) {
if p, ok := StringToPermission[s]; ok {
return p, nil
}
return 0, errors.New("invalid permission string")
}
// PermissionFromID converts an ID to its Permission enum
func PermissionFromID(id int) (Permission, error) {
if p, ok := IdToPermission[id]; ok {
return p, nil
}
return 0, errors.New("invalid permission ID")
}

View file

@ -0,0 +1,5 @@
permissions:
- configuration_access
- read
- delete
- create_and_update

View file

@ -1,6 +1,6 @@
[
{
"name": "Configuration Access",
"name": "configuration_access",
"test": {
"endpoint": "https://api.opsgenie.com/v2/account",
"method": "GET",
@ -9,7 +9,7 @@
}
},
{
"name": "Read",
"name": "read",
"test": {
"endpoint": "https://api.opsgenie.com/v2/alerts",
"method": "GET",
@ -18,7 +18,7 @@
}
},
{
"name": "Delete",
"name": "delete",
"test": {
"endpoint": "https://api.opsgenie.com/v2/alerts/`nowaythiscanexist",
"method": "DELETE",
@ -27,7 +27,7 @@
}
},
{
"name": "Create and Update",
"name": "create_and_update",
"test": {
"endpoint": "https://api.opsgenie.com/v2/alerts/`nowaycanthisexist/message",
"method": "PUT",

View file

@ -0,0 +1,76 @@
{
"AnalyzerType": 13,
"Bindings": [
{
"Resource": {
"Name": "rendy",
"FullyQualifiedName": "rendyplayground@gmail.com",
"Type": "user",
"Metadata": {
"email": "rendyplayground@gmail.com",
"role": "user",
"team_domain": "",
"team_name": "",
"username": "rendyplayground"
},
"Parent": null
},
"Permission": {
"Value": "usage_data:view",
"Parent": null
}
},
{
"Resource": {
"Name": "rendy",
"FullyQualifiedName": "rendyplayground@gmail.com",
"Type": "user",
"Metadata": {
"email": "rendyplayground@gmail.com",
"role": "user",
"team_domain": "",
"team_name": "",
"username": "rendyplayground"
},
"Parent": null
},
"Permission": {
"Value": "team_workspaces:create",
"Parent": null
}
},
{
"Resource": {
"Name": "rendy",
"FullyQualifiedName": "rendyplayground@gmail.com",
"Type": "user",
"Metadata": {
"email": "rendyplayground@gmail.com",
"role": "user",
"team_domain": "",
"team_name": "",
"username": "rendyplayground"
},
"Parent": null
},
"Permission": {
"Value": "team_workspaces:view",
"Parent": null
}
}
],
"UnboundedResources": [
{
"Name": "My Workspace",
"FullyQualifiedName": "4d06fc0c-6402-4a26-857d-80787b10eabf",
"Type": "workspace",
"Metadata": {
"id": "4d06fc0c-6402-4a26-857d-80787b10eabf",
"type": "personal",
"visibility": "personal"
},
"Parent": null
}
],
"Metadata": null
}

View file

@ -0,0 +1,181 @@
// Code generated by go generate; DO NOT EDIT.
package postman
import "errors"
type Permission int
const (
NoAccess Permission = iota
UserAdd Permission = iota
UserRemove Permission = iota
TeamAdminManage Permission = iota
TeamDevelopersManage Permission = iota
SsoManage Permission = iota
CustomDomainAdd Permission = iota
CustomDomainEdit Permission = iota
CustomDomainRemove Permission = iota
AuditLogsView Permission = iota
UsageDataView Permission = iota
BillingMembersManage Permission = iota
PaymentManage Permission = iota
PlanUpdate Permission = iota
TeamWorkspacesView Permission = iota
TeamWorkspacesCreate Permission = iota
TeamPublicProfileEnable Permission = iota
TeamPrivateApiNetworkManage Permission = iota
ParternerWorkspaceView Permission = iota
ParternerWorkspaceManage Permission = iota
ParternerWorkspaceVisibilityManage Permission = iota
PartnersManage Permission = iota
FlowAdd Permission = iota
FlowEdit Permission = iota
FlowRun Permission = iota
FlowPublish Permission = iota
)
var (
PermissionStrings = map[Permission]string{
UserAdd: "user:add",
UserRemove: "user:remove",
TeamAdminManage: "team_admin:manage",
TeamDevelopersManage: "team_developers:manage",
SsoManage: "sso:manage",
CustomDomainAdd: "custom_domain:add",
CustomDomainEdit: "custom_domain:edit",
CustomDomainRemove: "custom_domain:remove",
AuditLogsView: "audit_logs:view",
UsageDataView: "usage_data:view",
BillingMembersManage: "billing_members:manage",
PaymentManage: "payment:manage",
PlanUpdate: "plan:update",
TeamWorkspacesView: "team_workspaces:view",
TeamWorkspacesCreate: "team_workspaces:create",
TeamPublicProfileEnable: "team_public_profile:enable",
TeamPrivateApiNetworkManage: "team_private_api_network:manage",
ParternerWorkspaceView: "parterner_workspace:view",
ParternerWorkspaceManage: "parterner_workspace:manage",
ParternerWorkspaceVisibilityManage: "parterner_workspace_visibility:manage",
PartnersManage: "partners:manage",
FlowAdd: "flow:add",
FlowEdit: "flow:edit",
FlowRun: "flow:run",
FlowPublish: "flow:publish",
}
StringToPermission = map[string]Permission{
"user:add": UserAdd,
"user:remove": UserRemove,
"team_admin:manage": TeamAdminManage,
"team_developers:manage": TeamDevelopersManage,
"sso:manage": SsoManage,
"custom_domain:add": CustomDomainAdd,
"custom_domain:edit": CustomDomainEdit,
"custom_domain:remove": CustomDomainRemove,
"audit_logs:view": AuditLogsView,
"usage_data:view": UsageDataView,
"billing_members:manage": BillingMembersManage,
"payment:manage": PaymentManage,
"plan:update": PlanUpdate,
"team_workspaces:view": TeamWorkspacesView,
"team_workspaces:create": TeamWorkspacesCreate,
"team_public_profile:enable": TeamPublicProfileEnable,
"team_private_api_network:manage": TeamPrivateApiNetworkManage,
"parterner_workspace:view": ParternerWorkspaceView,
"parterner_workspace:manage": ParternerWorkspaceManage,
"parterner_workspace_visibility:manage": ParternerWorkspaceVisibilityManage,
"partners:manage": PartnersManage,
"flow:add": FlowAdd,
"flow:edit": FlowEdit,
"flow:run": FlowRun,
"flow:publish": FlowPublish,
}
PermissionIDs = map[Permission]int{
UserAdd: 0,
UserRemove: 1,
TeamAdminManage: 2,
TeamDevelopersManage: 3,
SsoManage: 4,
CustomDomainAdd: 5,
CustomDomainEdit: 6,
CustomDomainRemove: 7,
AuditLogsView: 8,
UsageDataView: 9,
BillingMembersManage: 10,
PaymentManage: 11,
PlanUpdate: 12,
TeamWorkspacesView: 13,
TeamWorkspacesCreate: 14,
TeamPublicProfileEnable: 15,
TeamPrivateApiNetworkManage: 16,
ParternerWorkspaceView: 17,
ParternerWorkspaceManage: 18,
ParternerWorkspaceVisibilityManage: 19,
PartnersManage: 20,
FlowAdd: 21,
FlowEdit: 22,
FlowRun: 23,
FlowPublish: 24,
}
IdToPermission = map[int]Permission{
0: UserAdd,
1: UserRemove,
2: TeamAdminManage,
3: TeamDevelopersManage,
4: SsoManage,
5: CustomDomainAdd,
6: CustomDomainEdit,
7: CustomDomainRemove,
8: AuditLogsView,
9: UsageDataView,
10: BillingMembersManage,
11: PaymentManage,
12: PlanUpdate,
13: TeamWorkspacesView,
14: TeamWorkspacesCreate,
15: TeamPublicProfileEnable,
16: TeamPrivateApiNetworkManage,
17: ParternerWorkspaceView,
18: ParternerWorkspaceManage,
19: ParternerWorkspaceVisibilityManage,
20: PartnersManage,
21: FlowAdd,
22: FlowEdit,
23: FlowRun,
24: FlowPublish,
}
)
// ToString converts a Permission enum to its string representation
func (p Permission) ToString() (string, error) {
if str, ok := PermissionStrings[p]; ok {
return str, nil
}
return "", errors.New("invalid permission")
}
// ToID converts a Permission enum to its ID
func (p Permission) ToID() (int, error) {
if id, ok := PermissionIDs[p]; ok {
return id, nil
}
return 0, errors.New("invalid permission")
}
// PermissionFromString converts a string representation to its Permission enum
func PermissionFromString(s string) (Permission, error) {
if p, ok := StringToPermission[s]; ok {
return p, nil
}
return 0, errors.New("invalid permission string")
}
// PermissionFromID converts an ID to its Permission enum
func PermissionFromID(id int) (Permission, error) {
if p, ok := IdToPermission[id]; ok {
return p, nil
}
return 0, errors.New("invalid permission ID")
}

View file

@ -0,0 +1,27 @@
permissions:
- user:add
- user:remove
- team_admin:manage
- team_developers:manage
- sso:manage
- custom_domain:add
- custom_domain:edit
- custom_domain:remove
- audit_logs:view
- usage_data:view
- billing_members:manage
- payment:manage
- plan:update
- team_workspaces:view
- team_workspaces:create
- team_public_profile:enable
- team_private_api_network:manage
- parterner_workspace:view
- parterner_workspace:manage
- parterner_workspace_visibility:manage
- partners:manage
- flow:add
- flow:edit
- flow:run
- flow:publish

View file

@ -1,3 +1,4 @@
//go:generate generate_permissions permissions.yaml permissions.go postman
package postman
import (
@ -5,13 +6,109 @@ import (
"fmt"
"net/http"
"os"
"strings"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/table"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
var _ analyzers.Analyzer = (*Analyzer)(nil)
type Analyzer struct {
Cfg *config.Config
}
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Postman }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
key, ok := credInfo["key"]
if !ok {
return nil, fmt.Errorf("missing key in credInfo")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return secretInfoToAnalyzerResult(info), nil
}
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzerpb.AnalyzerType_Postman,
Metadata: nil,
UnboundedResources: []analyzers.Resource{},
}
resource := analyzers.Resource{
Name: info.User.User.FullName,
FullyQualifiedName: info.User.User.Email,
Type: "user",
Metadata: map[string]any{
"role": strings.Join(info.User.User.Roles, ","),
"username": info.User.User.Username,
"email": info.User.User.Email,
"team_name": info.User.User.TeamName,
"team_domain": info.User.User.TeamDomain,
},
}
permissions := bakePermissions(info.User.User.Roles)
// bind all permissions with resources
result.Bindings = analyzers.BindAllPermissions(resource, permissions...)
for _, workspace := range info.Workspace.Workspaces {
result.UnboundedResources = append(result.UnboundedResources, analyzers.Resource{
Name: workspace.Name,
FullyQualifiedName: workspace.ID,
Type: "workspace",
Metadata: map[string]any{
"id": workspace.ID,
"type": workspace.Type,
"visibility": workspace.Visibility,
},
})
}
return &result
}
func bakePermissions(roles []string) []analyzers.Permission {
permissionMap := map[Permission]struct{}{}
for _, role := range roles {
permissions, ok := rolePermission[role]
if !ok {
continue
}
for _, permission := range permissions {
permissionMap[permission] = struct{}{}
}
}
permissions := make([]analyzers.Permission, 0, len(permissionMap))
for perm := range permissionMap {
permStr, err := perm.ToString()
if err != nil {
continue
}
permissions = append(permissions, analyzers.Permission{
Value: permStr,
Parent: nil,
})
}
return permissions
}
type UserInfoJSON struct {
User struct {
Username string `json:"username"`

View file

@ -0,0 +1,100 @@
package postman
import (
_ "embed"
"encoding/json"
"sort"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
//go:embed expected_output.json
var expectedOutput []byte
func TestAnalyzer_Analyze(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors3")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
tests := []struct {
name string
key string
want string // JSON string
wantErr bool
}{
{
name: "valid Postman key",
key: testSecrets.MustGetField("POSTMAN_TOKEN"),
want: string(expectedOutput),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := Analyzer{Cfg: &config.Config{}}
got, err := a.Analyze(ctx, map[string]string{"key": tt.key})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}
// bindings need to be in the same order to be comparable
sortBindings(got.Bindings)
// Marshal the actual result to JSON
gotJSON, err := json.Marshal(got)
if err != nil {
t.Fatalf("could not marshal got to JSON: %s", err)
}
// Parse the expected JSON string
var wantObj analyzers.AnalyzerResult
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
t.Fatalf("could not unmarshal want JSON string: %s", err)
}
// bindings need to be in the same order to be comparable
sortBindings(wantObj.Bindings)
// Marshal the expected result to JSON (to normalize)
wantJSON, err := json.Marshal(wantObj)
if err != nil {
t.Fatalf("could not marshal want to JSON: %s", err)
}
// Compare the JSON strings
if string(gotJSON) != string(wantJSON) {
// Pretty-print both JSON strings for easier comparison
var gotIndented, wantIndented []byte
gotIndented, err = json.MarshalIndent(got, "", " ")
if err != nil {
t.Fatalf("could not marshal got to indented JSON: %s", err)
}
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
if err != nil {
t.Fatalf("could not marshal want to indented JSON: %s", err)
}
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
}
})
}
}
// Helper function to sort bindings
func sortBindings(bindings []analyzers.Binding) {
sort.SliceStable(bindings, func(i, j int) bool {
if bindings[i].Resource.Name == bindings[j].Resource.Name {
return bindings[i].Permission.Value < bindings[j].Permission.Value
}
return bindings[i].Resource.Name < bindings[j].Resource.Name
})
}

View file

@ -11,3 +11,93 @@ var roleDescriptions = map[string]string{
"guest": "Views collections and sends requests in collections that have been shared with them. This role can't be directly assigned to a user.",
"flow-editor": "(Basic and Professional plans only) - Can create, edit, run, and publish Postman Flows.",
}
var rolePermission = map[string][]Permission{
"super-admin": {
UserAdd,
UserRemove,
TeamAdminManage,
TeamDevelopersManage,
SsoManage,
CustomDomainAdd,
CustomDomainEdit,
CustomDomainRemove,
AuditLogsView,
UsageDataView,
BillingMembersManage,
PaymentManage,
PlanUpdate,
TeamWorkspacesView,
TeamWorkspacesCreate,
TeamPublicProfileEnable,
TeamPrivateApiNetworkManage,
PartnersManage,
ParternerWorkspaceManage,
ParternerWorkspaceView,
ParternerWorkspaceVisibilityManage,
FlowAdd,
FlowEdit,
FlowRun,
FlowPublish,
},
"admin": {
UserAdd,
UserRemove,
TeamAdminManage,
TeamDevelopersManage,
SsoManage,
CustomDomainAdd,
CustomDomainEdit,
CustomDomainRemove,
AuditLogsView,
UsageDataView,
BillingMembersManage,
TeamPublicProfileEnable,
PartnersManage,
ParternerWorkspaceManage,
ParternerWorkspaceView,
ParternerWorkspaceVisibilityManage,
FlowAdd,
FlowEdit,
FlowRun,
FlowPublish,
},
"billing": {
UsageDataView,
BillingMembersManage,
PaymentManage,
PlanUpdate,
},
"user": {
UsageDataView,
TeamWorkspacesCreate,
TeamWorkspacesView,
},
"community-manager": {
CustomDomainAdd,
CustomDomainEdit,
AuditLogsView,
UsageDataView,
TeamWorkspacesView,
TeamWorkspacesCreate,
TeamPublicProfileEnable,
},
"partner-manager": {
PartnersManage,
ParternerWorkspaceManage,
ParternerWorkspaceView,
ParternerWorkspaceVisibilityManage,
},
"partner": {
ParternerWorkspaceView,
},
"guest": {
TeamWorkspacesView,
},
"flow-editor": {
FlowAdd,
FlowEdit,
FlowRun,
FlowPublish,
},
}

View file

@ -0,0 +1,991 @@
// Code generated by go generate; DO NOT EDIT.
package sendgrid
import "errors"
type Permission int
const (
Invalid Permission = iota
AccessSettingsActivityRead Permission = iota
AccessSettingsWhitelistCreate Permission = iota
AccessSettingsWhitelistDelete Permission = iota
AccessSettingsWhitelistRead Permission = iota
AccessSettingsWhitelistUpdate Permission = iota
AlertsCreate Permission = iota
AlertsDelete Permission = iota
AlertsRead Permission = iota
AlertsUpdate Permission = iota
ApiKeysCreate Permission = iota
ApiKeysDelete Permission = iota
ApiKeysRead Permission = iota
ApiKeysUpdate Permission = iota
AsmGroupsCreate Permission = iota
AsmGroupsDelete Permission = iota
AsmGroupsRead Permission = iota
AsmGroupsUpdate Permission = iota
BillingCreate Permission = iota
BillingDelete Permission = iota
BillingRead Permission = iota
BillingUpdate Permission = iota
BrowsersStatsRead Permission = iota
CategoriesCreate Permission = iota
CategoriesDelete Permission = iota
CategoriesRead Permission = iota
CategoriesStatsRead Permission = iota
CategoriesStatsSumsRead Permission = iota
CategoriesUpdate Permission = iota
ClientsDesktopStatsRead Permission = iota
ClientsPhoneStatsRead Permission = iota
ClientsStatsRead Permission = iota
ClientsTabletStatsRead Permission = iota
ClientsWebmailStatsRead Permission = iota
DevicesStatsRead Permission = iota
EmailActivityRead Permission = iota
GeoStatsRead Permission = iota
IpsAssignedRead Permission = iota
IpsPoolsCreate Permission = iota
IpsPoolsDelete Permission = iota
IpsPoolsIpsCreate Permission = iota
IpsPoolsIpsDelete Permission = iota
IpsPoolsIpsRead Permission = iota
IpsPoolsIpsUpdate Permission = iota
IpsPoolsRead Permission = iota
IpsPoolsUpdate Permission = iota
IpsRead Permission = iota
IpsWarmupCreate Permission = iota
IpsWarmupDelete Permission = iota
IpsWarmupRead Permission = iota
IpsWarmupUpdate Permission = iota
MailSettingsAddressWhitelistRead Permission = iota
MailSettingsAddressWhitelistUpdate Permission = iota
MailSettingsBouncePurgeRead Permission = iota
MailSettingsBouncePurgeUpdate Permission = iota
MailSettingsFooterRead Permission = iota
MailSettingsFooterUpdate Permission = iota
MailSettingsForwardBounceRead Permission = iota
MailSettingsForwardBounceUpdate Permission = iota
MailSettingsForwardSpamRead Permission = iota
MailSettingsForwardSpamUpdate Permission = iota
MailSettingsPlainContentRead Permission = iota
MailSettingsPlainContentUpdate Permission = iota
MailSettingsRead Permission = iota
MailSettingsTemplateRead Permission = iota
MailSettingsTemplateUpdate Permission = iota
MailBatchCreate Permission = iota
MailBatchDelete Permission = iota
MailBatchRead Permission = iota
MailBatchUpdate Permission = iota
MailSend Permission = iota
MailboxProvidersStatsRead Permission = iota
MarketingCampaignsCreate Permission = iota
MarketingCampaignsDelete Permission = iota
MarketingCampaignsRead Permission = iota
MarketingCampaignsUpdate Permission = iota
PartnerSettingsNewRelicRead Permission = iota
PartnerSettingsNewRelicUpdate Permission = iota
PartnerSettingsRead Permission = iota
StatsGlobalRead Permission = iota
StatsRead Permission = iota
SubusersCreate Permission = iota
SubusersCreditsCreate Permission = iota
SubusersCreditsDelete Permission = iota
SubusersCreditsRead Permission = iota
SubusersCreditsRemainingCreate Permission = iota
SubusersCreditsRemainingDelete Permission = iota
SubusersCreditsRemainingRead Permission = iota
SubusersCreditsRemainingUpdate Permission = iota
SubusersCreditsUpdate Permission = iota
SubusersDelete Permission = iota
SubusersMonitorCreate Permission = iota
SubusersMonitorDelete Permission = iota
SubusersMonitorRead Permission = iota
SubusersMonitorUpdate Permission = iota
SubusersRead Permission = iota
SubusersReputationsRead Permission = iota
SubusersStatsMonthlyRead Permission = iota
SubusersStatsRead Permission = iota
SubusersStatsSumsRead Permission = iota
SubusersSummaryRead Permission = iota
SubusersUpdate Permission = iota
SuppressionBlocksCreate Permission = iota
SuppressionBlocksDelete Permission = iota
SuppressionBlocksRead Permission = iota
SuppressionBlocksUpdate Permission = iota
SuppressionBouncesCreate Permission = iota
SuppressionBouncesDelete Permission = iota
SuppressionBouncesRead Permission = iota
SuppressionBouncesUpdate Permission = iota
SuppressionCreate Permission = iota
SuppressionDelete Permission = iota
SuppressionInvalidEmailsCreate Permission = iota
SuppressionInvalidEmailsDelete Permission = iota
SuppressionInvalidEmailsRead Permission = iota
SuppressionInvalidEmailsUpdate Permission = iota
SuppressionRead Permission = iota
SuppressionSpamReportsCreate Permission = iota
SuppressionSpamReportsDelete Permission = iota
SuppressionSpamReportsRead Permission = iota
SuppressionSpamReportsUpdate Permission = iota
SuppressionUnsubscribesCreate Permission = iota
SuppressionUnsubscribesDelete Permission = iota
SuppressionUnsubscribesRead Permission = iota
SuppressionUnsubscribesUpdate Permission = iota
SuppressionUpdate Permission = iota
TeammatesCreate Permission = iota
TeammatesRead Permission = iota
TeammatesUpdate Permission = iota
TeammatesDelete Permission = iota
TemplatesCreate Permission = iota
TemplatesDelete Permission = iota
TemplatesRead Permission = iota
TemplatesUpdate Permission = iota
TemplatesVersionsActivateCreate Permission = iota
TemplatesVersionsActivateDelete Permission = iota
TemplatesVersionsActivateRead Permission = iota
TemplatesVersionsActivateUpdate Permission = iota
TemplatesVersionsCreate Permission = iota
TemplatesVersionsDelete Permission = iota
TemplatesVersionsRead Permission = iota
TemplatesVersionsUpdate Permission = iota
TrackingSettingsClickRead Permission = iota
TrackingSettingsClickUpdate Permission = iota
TrackingSettingsGoogleAnalyticsRead Permission = iota
TrackingSettingsGoogleAnalyticsUpdate Permission = iota
TrackingSettingsOpenRead Permission = iota
TrackingSettingsOpenUpdate Permission = iota
TrackingSettingsRead Permission = iota
TrackingSettingsSubscriptionRead Permission = iota
TrackingSettingsSubscriptionUpdate Permission = iota
UserAccountRead Permission = iota
UserCreditsRead Permission = iota
UserEmailCreate Permission = iota
UserEmailDelete Permission = iota
UserEmailRead Permission = iota
UserEmailUpdate Permission = iota
UserMultifactorAuthenticationCreate Permission = iota
UserMultifactorAuthenticationDelete Permission = iota
UserMultifactorAuthenticationRead Permission = iota
UserMultifactorAuthenticationUpdate Permission = iota
UserPasswordRead Permission = iota
UserPasswordUpdate Permission = iota
UserProfileRead Permission = iota
UserProfileUpdate Permission = iota
UserScheduledSendsCreate Permission = iota
UserScheduledSendsDelete Permission = iota
UserScheduledSendsRead Permission = iota
UserScheduledSendsUpdate Permission = iota
UserSettingsEnforcedTlsRead Permission = iota
UserSettingsEnforcedTlsUpdate Permission = iota
UserTimezoneRead Permission = iota
UserUsernameRead Permission = iota
UserUsernameUpdate Permission = iota
UserWebhooksEventSettingsRead Permission = iota
UserWebhooksEventSettingsUpdate Permission = iota
UserWebhooksEventTestCreate Permission = iota
UserWebhooksEventTestRead Permission = iota
UserWebhooksEventTestUpdate Permission = iota
UserWebhooksParseSettingsCreate Permission = iota
UserWebhooksParseSettingsDelete Permission = iota
UserWebhooksParseSettingsRead Permission = iota
UserWebhooksParseSettingsUpdate Permission = iota
UserWebhooksParseStatsRead Permission = iota
WhitelabelCreate Permission = iota
WhitelabelDelete Permission = iota
WhitelabelRead Permission = iota
WhitelabelUpdate Permission = iota
)
var (
PermissionStrings = map[Permission]string{
AccessSettingsActivityRead: "access_settings.activity.read",
AccessSettingsWhitelistCreate: "access_settings.whitelist.create",
AccessSettingsWhitelistDelete: "access_settings.whitelist.delete",
AccessSettingsWhitelistRead: "access_settings.whitelist.read",
AccessSettingsWhitelistUpdate: "access_settings.whitelist.update",
AlertsCreate: "alerts.create",
AlertsDelete: "alerts.delete",
AlertsRead: "alerts.read",
AlertsUpdate: "alerts.update",
ApiKeysCreate: "api_keys.create",
ApiKeysDelete: "api_keys.delete",
ApiKeysRead: "api_keys.read",
ApiKeysUpdate: "api_keys.update",
AsmGroupsCreate: "asm.groups.create",
AsmGroupsDelete: "asm.groups.delete",
AsmGroupsRead: "asm.groups.read",
AsmGroupsUpdate: "asm.groups.update",
BillingCreate: "billing.create",
BillingDelete: "billing.delete",
BillingRead: "billing.read",
BillingUpdate: "billing.update",
BrowsersStatsRead: "browsers.stats.read",
CategoriesCreate: "categories.create",
CategoriesDelete: "categories.delete",
CategoriesRead: "categories.read",
CategoriesStatsRead: "categories.stats.read",
CategoriesStatsSumsRead: "categories.stats.sums.read",
CategoriesUpdate: "categories.update",
ClientsDesktopStatsRead: "clients.desktop.stats.read",
ClientsPhoneStatsRead: "clients.phone.stats.read",
ClientsStatsRead: "clients.stats.read",
ClientsTabletStatsRead: "clients.tablet.stats.read",
ClientsWebmailStatsRead: "clients.webmail.stats.read",
DevicesStatsRead: "devices.stats.read",
EmailActivityRead: "email_activity.read",
GeoStatsRead: "geo.stats.read",
IpsAssignedRead: "ips.assigned.read",
IpsPoolsCreate: "ips.pools.create",
IpsPoolsDelete: "ips.pools.delete",
IpsPoolsIpsCreate: "ips.pools.ips.create",
IpsPoolsIpsDelete: "ips.pools.ips.delete",
IpsPoolsIpsRead: "ips.pools.ips.read",
IpsPoolsIpsUpdate: "ips.pools.ips.update",
IpsPoolsRead: "ips.pools.read",
IpsPoolsUpdate: "ips.pools.update",
IpsRead: "ips.read",
IpsWarmupCreate: "ips.warmup.create",
IpsWarmupDelete: "ips.warmup.delete",
IpsWarmupRead: "ips.warmup.read",
IpsWarmupUpdate: "ips.warmup.update",
MailSettingsAddressWhitelistRead: "mail_settings.address_whitelist.read",
MailSettingsAddressWhitelistUpdate: "mail_settings.address_whitelist.update",
MailSettingsBouncePurgeRead: "mail_settings.bounce_purge.read",
MailSettingsBouncePurgeUpdate: "mail_settings.bounce_purge.update",
MailSettingsFooterRead: "mail_settings.footer.read",
MailSettingsFooterUpdate: "mail_settings.footer.update",
MailSettingsForwardBounceRead: "mail_settings.forward_bounce.read",
MailSettingsForwardBounceUpdate: "mail_settings.forward_bounce.update",
MailSettingsForwardSpamRead: "mail_settings.forward_spam.read",
MailSettingsForwardSpamUpdate: "mail_settings.forward_spam.update",
MailSettingsPlainContentRead: "mail_settings.plain_content.read",
MailSettingsPlainContentUpdate: "mail_settings.plain_content.update",
MailSettingsRead: "mail_settings.read",
MailSettingsTemplateRead: "mail_settings.template.read",
MailSettingsTemplateUpdate: "mail_settings.template.update",
MailBatchCreate: "mail.batch.create",
MailBatchDelete: "mail.batch.delete",
MailBatchRead: "mail.batch.read",
MailBatchUpdate: "mail.batch.update",
MailSend: "mail.send",
MailboxProvidersStatsRead: "mailbox_providers.stats.read",
MarketingCampaignsCreate: "marketing_campaigns.create",
MarketingCampaignsDelete: "marketing_campaigns.delete",
MarketingCampaignsRead: "marketing_campaigns.read",
MarketingCampaignsUpdate: "marketing_campaigns.update",
PartnerSettingsNewRelicRead: "partner_settings.new_relic.read",
PartnerSettingsNewRelicUpdate: "partner_settings.new_relic.update",
PartnerSettingsRead: "partner_settings.read",
StatsGlobalRead: "stats.global.read",
StatsRead: "stats.read",
SubusersCreate: "subusers.create",
SubusersCreditsCreate: "subusers.credits.create",
SubusersCreditsDelete: "subusers.credits.delete",
SubusersCreditsRead: "subusers.credits.read",
SubusersCreditsRemainingCreate: "subusers.credits.remaining.create",
SubusersCreditsRemainingDelete: "subusers.credits.remaining.delete",
SubusersCreditsRemainingRead: "subusers.credits.remaining.read",
SubusersCreditsRemainingUpdate: "subusers.credits.remaining.update",
SubusersCreditsUpdate: "subusers.credits.update",
SubusersDelete: "subusers.delete",
SubusersMonitorCreate: "subusers.monitor.create",
SubusersMonitorDelete: "subusers.monitor.delete",
SubusersMonitorRead: "subusers.monitor.read",
SubusersMonitorUpdate: "subusers.monitor.update",
SubusersRead: "subusers.read",
SubusersReputationsRead: "subusers.reputations.read",
SubusersStatsMonthlyRead: "subusers.stats.monthly.read",
SubusersStatsRead: "subusers.stats.read",
SubusersStatsSumsRead: "subusers.stats.sums.read",
SubusersSummaryRead: "subusers.summary.read",
SubusersUpdate: "subusers.update",
SuppressionBlocksCreate: "suppression.blocks.create",
SuppressionBlocksDelete: "suppression.blocks.delete",
SuppressionBlocksRead: "suppression.blocks.read",
SuppressionBlocksUpdate: "suppression.blocks.update",
SuppressionBouncesCreate: "suppression.bounces.create",
SuppressionBouncesDelete: "suppression.bounces.delete",
SuppressionBouncesRead: "suppression.bounces.read",
SuppressionBouncesUpdate: "suppression.bounces.update",
SuppressionCreate: "suppression.create",
SuppressionDelete: "suppression.delete",
SuppressionInvalidEmailsCreate: "suppression.invalid_emails.create",
SuppressionInvalidEmailsDelete: "suppression.invalid_emails.delete",
SuppressionInvalidEmailsRead: "suppression.invalid_emails.read",
SuppressionInvalidEmailsUpdate: "suppression.invalid_emails.update",
SuppressionRead: "suppression.read",
SuppressionSpamReportsCreate: "suppression.spam_reports.create",
SuppressionSpamReportsDelete: "suppression.spam_reports.delete",
SuppressionSpamReportsRead: "suppression.spam_reports.read",
SuppressionSpamReportsUpdate: "suppression.spam_reports.update",
SuppressionUnsubscribesCreate: "suppression.unsubscribes.create",
SuppressionUnsubscribesDelete: "suppression.unsubscribes.delete",
SuppressionUnsubscribesRead: "suppression.unsubscribes.read",
SuppressionUnsubscribesUpdate: "suppression.unsubscribes.update",
SuppressionUpdate: "suppression.update",
TeammatesCreate: "teammates.create",
TeammatesRead: "teammates.read",
TeammatesUpdate: "teammates.update",
TeammatesDelete: "teammates.delete",
TemplatesCreate: "templates.create",
TemplatesDelete: "templates.delete",
TemplatesRead: "templates.read",
TemplatesUpdate: "templates.update",
TemplatesVersionsActivateCreate: "templates.versions.activate.create",
TemplatesVersionsActivateDelete: "templates.versions.activate.delete",
TemplatesVersionsActivateRead: "templates.versions.activate.read",
TemplatesVersionsActivateUpdate: "templates.versions.activate.update",
TemplatesVersionsCreate: "templates.versions.create",
TemplatesVersionsDelete: "templates.versions.delete",
TemplatesVersionsRead: "templates.versions.read",
TemplatesVersionsUpdate: "templates.versions.update",
TrackingSettingsClickRead: "tracking_settings.click.read",
TrackingSettingsClickUpdate: "tracking_settings.click.update",
TrackingSettingsGoogleAnalyticsRead: "tracking_settings.google_analytics.read",
TrackingSettingsGoogleAnalyticsUpdate: "tracking_settings.google_analytics.update",
TrackingSettingsOpenRead: "tracking_settings.open.read",
TrackingSettingsOpenUpdate: "tracking_settings.open.update",
TrackingSettingsRead: "tracking_settings.read",
TrackingSettingsSubscriptionRead: "tracking_settings.subscription.read",
TrackingSettingsSubscriptionUpdate: "tracking_settings.subscription.update",
UserAccountRead: "user.account.read",
UserCreditsRead: "user.credits.read",
UserEmailCreate: "user.email.create",
UserEmailDelete: "user.email.delete",
UserEmailRead: "user.email.read",
UserEmailUpdate: "user.email.update",
UserMultifactorAuthenticationCreate: "user.multifactor_authentication.create",
UserMultifactorAuthenticationDelete: "user.multifactor_authentication.delete",
UserMultifactorAuthenticationRead: "user.multifactor_authentication.read",
UserMultifactorAuthenticationUpdate: "user.multifactor_authentication.update",
UserPasswordRead: "user.password.read",
UserPasswordUpdate: "user.password.update",
UserProfileRead: "user.profile.read",
UserProfileUpdate: "user.profile.update",
UserScheduledSendsCreate: "user.scheduled_sends.create",
UserScheduledSendsDelete: "user.scheduled_sends.delete",
UserScheduledSendsRead: "user.scheduled_sends.read",
UserScheduledSendsUpdate: "user.scheduled_sends.update",
UserSettingsEnforcedTlsRead: "user.settings.enforced_tls.read",
UserSettingsEnforcedTlsUpdate: "user.settings.enforced_tls.update",
UserTimezoneRead: "user.timezone.read",
UserUsernameRead: "user.username.read",
UserUsernameUpdate: "user.username.update",
UserWebhooksEventSettingsRead: "user.webhooks.event.settings.read",
UserWebhooksEventSettingsUpdate: "user.webhooks.event.settings.update",
UserWebhooksEventTestCreate: "user.webhooks.event.test.create",
UserWebhooksEventTestRead: "user.webhooks.event.test.read",
UserWebhooksEventTestUpdate: "user.webhooks.event.test.update",
UserWebhooksParseSettingsCreate: "user.webhooks.parse.settings.create",
UserWebhooksParseSettingsDelete: "user.webhooks.parse.settings.delete",
UserWebhooksParseSettingsRead: "user.webhooks.parse.settings.read",
UserWebhooksParseSettingsUpdate: "user.webhooks.parse.settings.update",
UserWebhooksParseStatsRead: "user.webhooks.parse.stats.read",
WhitelabelCreate: "whitelabel.create",
WhitelabelDelete: "whitelabel.delete",
WhitelabelRead: "whitelabel.read",
WhitelabelUpdate: "whitelabel.update",
}
StringToPermission = map[string]Permission{
"access_settings.activity.read": AccessSettingsActivityRead,
"access_settings.whitelist.create": AccessSettingsWhitelistCreate,
"access_settings.whitelist.delete": AccessSettingsWhitelistDelete,
"access_settings.whitelist.read": AccessSettingsWhitelistRead,
"access_settings.whitelist.update": AccessSettingsWhitelistUpdate,
"alerts.create": AlertsCreate,
"alerts.delete": AlertsDelete,
"alerts.read": AlertsRead,
"alerts.update": AlertsUpdate,
"api_keys.create": ApiKeysCreate,
"api_keys.delete": ApiKeysDelete,
"api_keys.read": ApiKeysRead,
"api_keys.update": ApiKeysUpdate,
"asm.groups.create": AsmGroupsCreate,
"asm.groups.delete": AsmGroupsDelete,
"asm.groups.read": AsmGroupsRead,
"asm.groups.update": AsmGroupsUpdate,
"billing.create": BillingCreate,
"billing.delete": BillingDelete,
"billing.read": BillingRead,
"billing.update": BillingUpdate,
"browsers.stats.read": BrowsersStatsRead,
"categories.create": CategoriesCreate,
"categories.delete": CategoriesDelete,
"categories.read": CategoriesRead,
"categories.stats.read": CategoriesStatsRead,
"categories.stats.sums.read": CategoriesStatsSumsRead,
"categories.update": CategoriesUpdate,
"clients.desktop.stats.read": ClientsDesktopStatsRead,
"clients.phone.stats.read": ClientsPhoneStatsRead,
"clients.stats.read": ClientsStatsRead,
"clients.tablet.stats.read": ClientsTabletStatsRead,
"clients.webmail.stats.read": ClientsWebmailStatsRead,
"devices.stats.read": DevicesStatsRead,
"email_activity.read": EmailActivityRead,
"geo.stats.read": GeoStatsRead,
"ips.assigned.read": IpsAssignedRead,
"ips.pools.create": IpsPoolsCreate,
"ips.pools.delete": IpsPoolsDelete,
"ips.pools.ips.create": IpsPoolsIpsCreate,
"ips.pools.ips.delete": IpsPoolsIpsDelete,
"ips.pools.ips.read": IpsPoolsIpsRead,
"ips.pools.ips.update": IpsPoolsIpsUpdate,
"ips.pools.read": IpsPoolsRead,
"ips.pools.update": IpsPoolsUpdate,
"ips.read": IpsRead,
"ips.warmup.create": IpsWarmupCreate,
"ips.warmup.delete": IpsWarmupDelete,
"ips.warmup.read": IpsWarmupRead,
"ips.warmup.update": IpsWarmupUpdate,
"mail_settings.address_whitelist.read": MailSettingsAddressWhitelistRead,
"mail_settings.address_whitelist.update": MailSettingsAddressWhitelistUpdate,
"mail_settings.bounce_purge.read": MailSettingsBouncePurgeRead,
"mail_settings.bounce_purge.update": MailSettingsBouncePurgeUpdate,
"mail_settings.footer.read": MailSettingsFooterRead,
"mail_settings.footer.update": MailSettingsFooterUpdate,
"mail_settings.forward_bounce.read": MailSettingsForwardBounceRead,
"mail_settings.forward_bounce.update": MailSettingsForwardBounceUpdate,
"mail_settings.forward_spam.read": MailSettingsForwardSpamRead,
"mail_settings.forward_spam.update": MailSettingsForwardSpamUpdate,
"mail_settings.plain_content.read": MailSettingsPlainContentRead,
"mail_settings.plain_content.update": MailSettingsPlainContentUpdate,
"mail_settings.read": MailSettingsRead,
"mail_settings.template.read": MailSettingsTemplateRead,
"mail_settings.template.update": MailSettingsTemplateUpdate,
"mail.batch.create": MailBatchCreate,
"mail.batch.delete": MailBatchDelete,
"mail.batch.read": MailBatchRead,
"mail.batch.update": MailBatchUpdate,
"mail.send": MailSend,
"mailbox_providers.stats.read": MailboxProvidersStatsRead,
"marketing_campaigns.create": MarketingCampaignsCreate,
"marketing_campaigns.delete": MarketingCampaignsDelete,
"marketing_campaigns.read": MarketingCampaignsRead,
"marketing_campaigns.update": MarketingCampaignsUpdate,
"partner_settings.new_relic.read": PartnerSettingsNewRelicRead,
"partner_settings.new_relic.update": PartnerSettingsNewRelicUpdate,
"partner_settings.read": PartnerSettingsRead,
"stats.global.read": StatsGlobalRead,
"stats.read": StatsRead,
"subusers.create": SubusersCreate,
"subusers.credits.create": SubusersCreditsCreate,
"subusers.credits.delete": SubusersCreditsDelete,
"subusers.credits.read": SubusersCreditsRead,
"subusers.credits.remaining.create": SubusersCreditsRemainingCreate,
"subusers.credits.remaining.delete": SubusersCreditsRemainingDelete,
"subusers.credits.remaining.read": SubusersCreditsRemainingRead,
"subusers.credits.remaining.update": SubusersCreditsRemainingUpdate,
"subusers.credits.update": SubusersCreditsUpdate,
"subusers.delete": SubusersDelete,
"subusers.monitor.create": SubusersMonitorCreate,
"subusers.monitor.delete": SubusersMonitorDelete,
"subusers.monitor.read": SubusersMonitorRead,
"subusers.monitor.update": SubusersMonitorUpdate,
"subusers.read": SubusersRead,
"subusers.reputations.read": SubusersReputationsRead,
"subusers.stats.monthly.read": SubusersStatsMonthlyRead,
"subusers.stats.read": SubusersStatsRead,
"subusers.stats.sums.read": SubusersStatsSumsRead,
"subusers.summary.read": SubusersSummaryRead,
"subusers.update": SubusersUpdate,
"suppression.blocks.create": SuppressionBlocksCreate,
"suppression.blocks.delete": SuppressionBlocksDelete,
"suppression.blocks.read": SuppressionBlocksRead,
"suppression.blocks.update": SuppressionBlocksUpdate,
"suppression.bounces.create": SuppressionBouncesCreate,
"suppression.bounces.delete": SuppressionBouncesDelete,
"suppression.bounces.read": SuppressionBouncesRead,
"suppression.bounces.update": SuppressionBouncesUpdate,
"suppression.create": SuppressionCreate,
"suppression.delete": SuppressionDelete,
"suppression.invalid_emails.create": SuppressionInvalidEmailsCreate,
"suppression.invalid_emails.delete": SuppressionInvalidEmailsDelete,
"suppression.invalid_emails.read": SuppressionInvalidEmailsRead,
"suppression.invalid_emails.update": SuppressionInvalidEmailsUpdate,
"suppression.read": SuppressionRead,
"suppression.spam_reports.create": SuppressionSpamReportsCreate,
"suppression.spam_reports.delete": SuppressionSpamReportsDelete,
"suppression.spam_reports.read": SuppressionSpamReportsRead,
"suppression.spam_reports.update": SuppressionSpamReportsUpdate,
"suppression.unsubscribes.create": SuppressionUnsubscribesCreate,
"suppression.unsubscribes.delete": SuppressionUnsubscribesDelete,
"suppression.unsubscribes.read": SuppressionUnsubscribesRead,
"suppression.unsubscribes.update": SuppressionUnsubscribesUpdate,
"suppression.update": SuppressionUpdate,
"teammates.create": TeammatesCreate,
"teammates.read": TeammatesRead,
"teammates.update": TeammatesUpdate,
"teammates.delete": TeammatesDelete,
"templates.create": TemplatesCreate,
"templates.delete": TemplatesDelete,
"templates.read": TemplatesRead,
"templates.update": TemplatesUpdate,
"templates.versions.activate.create": TemplatesVersionsActivateCreate,
"templates.versions.activate.delete": TemplatesVersionsActivateDelete,
"templates.versions.activate.read": TemplatesVersionsActivateRead,
"templates.versions.activate.update": TemplatesVersionsActivateUpdate,
"templates.versions.create": TemplatesVersionsCreate,
"templates.versions.delete": TemplatesVersionsDelete,
"templates.versions.read": TemplatesVersionsRead,
"templates.versions.update": TemplatesVersionsUpdate,
"tracking_settings.click.read": TrackingSettingsClickRead,
"tracking_settings.click.update": TrackingSettingsClickUpdate,
"tracking_settings.google_analytics.read": TrackingSettingsGoogleAnalyticsRead,
"tracking_settings.google_analytics.update": TrackingSettingsGoogleAnalyticsUpdate,
"tracking_settings.open.read": TrackingSettingsOpenRead,
"tracking_settings.open.update": TrackingSettingsOpenUpdate,
"tracking_settings.read": TrackingSettingsRead,
"tracking_settings.subscription.read": TrackingSettingsSubscriptionRead,
"tracking_settings.subscription.update": TrackingSettingsSubscriptionUpdate,
"user.account.read": UserAccountRead,
"user.credits.read": UserCreditsRead,
"user.email.create": UserEmailCreate,
"user.email.delete": UserEmailDelete,
"user.email.read": UserEmailRead,
"user.email.update": UserEmailUpdate,
"user.multifactor_authentication.create": UserMultifactorAuthenticationCreate,
"user.multifactor_authentication.delete": UserMultifactorAuthenticationDelete,
"user.multifactor_authentication.read": UserMultifactorAuthenticationRead,
"user.multifactor_authentication.update": UserMultifactorAuthenticationUpdate,
"user.password.read": UserPasswordRead,
"user.password.update": UserPasswordUpdate,
"user.profile.read": UserProfileRead,
"user.profile.update": UserProfileUpdate,
"user.scheduled_sends.create": UserScheduledSendsCreate,
"user.scheduled_sends.delete": UserScheduledSendsDelete,
"user.scheduled_sends.read": UserScheduledSendsRead,
"user.scheduled_sends.update": UserScheduledSendsUpdate,
"user.settings.enforced_tls.read": UserSettingsEnforcedTlsRead,
"user.settings.enforced_tls.update": UserSettingsEnforcedTlsUpdate,
"user.timezone.read": UserTimezoneRead,
"user.username.read": UserUsernameRead,
"user.username.update": UserUsernameUpdate,
"user.webhooks.event.settings.read": UserWebhooksEventSettingsRead,
"user.webhooks.event.settings.update": UserWebhooksEventSettingsUpdate,
"user.webhooks.event.test.create": UserWebhooksEventTestCreate,
"user.webhooks.event.test.read": UserWebhooksEventTestRead,
"user.webhooks.event.test.update": UserWebhooksEventTestUpdate,
"user.webhooks.parse.settings.create": UserWebhooksParseSettingsCreate,
"user.webhooks.parse.settings.delete": UserWebhooksParseSettingsDelete,
"user.webhooks.parse.settings.read": UserWebhooksParseSettingsRead,
"user.webhooks.parse.settings.update": UserWebhooksParseSettingsUpdate,
"user.webhooks.parse.stats.read": UserWebhooksParseStatsRead,
"whitelabel.create": WhitelabelCreate,
"whitelabel.delete": WhitelabelDelete,
"whitelabel.read": WhitelabelRead,
"whitelabel.update": WhitelabelUpdate,
}
PermissionIDs = map[Permission]int{
AccessSettingsActivityRead: 1,
AccessSettingsWhitelistCreate: 2,
AccessSettingsWhitelistDelete: 3,
AccessSettingsWhitelistRead: 4,
AccessSettingsWhitelistUpdate: 5,
AlertsCreate: 6,
AlertsDelete: 7,
AlertsRead: 8,
AlertsUpdate: 9,
ApiKeysCreate: 10,
ApiKeysDelete: 11,
ApiKeysRead: 12,
ApiKeysUpdate: 13,
AsmGroupsCreate: 14,
AsmGroupsDelete: 15,
AsmGroupsRead: 16,
AsmGroupsUpdate: 17,
BillingCreate: 18,
BillingDelete: 19,
BillingRead: 20,
BillingUpdate: 21,
BrowsersStatsRead: 22,
CategoriesCreate: 23,
CategoriesDelete: 24,
CategoriesRead: 25,
CategoriesStatsRead: 26,
CategoriesStatsSumsRead: 27,
CategoriesUpdate: 28,
ClientsDesktopStatsRead: 29,
ClientsPhoneStatsRead: 30,
ClientsStatsRead: 31,
ClientsTabletStatsRead: 32,
ClientsWebmailStatsRead: 33,
DevicesStatsRead: 34,
EmailActivityRead: 35,
GeoStatsRead: 36,
IpsAssignedRead: 37,
IpsPoolsCreate: 38,
IpsPoolsDelete: 39,
IpsPoolsIpsCreate: 40,
IpsPoolsIpsDelete: 41,
IpsPoolsIpsRead: 42,
IpsPoolsIpsUpdate: 43,
IpsPoolsRead: 44,
IpsPoolsUpdate: 45,
IpsRead: 46,
IpsWarmupCreate: 47,
IpsWarmupDelete: 48,
IpsWarmupRead: 49,
IpsWarmupUpdate: 50,
MailSettingsAddressWhitelistRead: 51,
MailSettingsAddressWhitelistUpdate: 52,
MailSettingsBouncePurgeRead: 53,
MailSettingsBouncePurgeUpdate: 54,
MailSettingsFooterRead: 55,
MailSettingsFooterUpdate: 56,
MailSettingsForwardBounceRead: 57,
MailSettingsForwardBounceUpdate: 58,
MailSettingsForwardSpamRead: 59,
MailSettingsForwardSpamUpdate: 60,
MailSettingsPlainContentRead: 61,
MailSettingsPlainContentUpdate: 62,
MailSettingsRead: 63,
MailSettingsTemplateRead: 64,
MailSettingsTemplateUpdate: 65,
MailBatchCreate: 66,
MailBatchDelete: 67,
MailBatchRead: 68,
MailBatchUpdate: 69,
MailSend: 70,
MailboxProvidersStatsRead: 71,
MarketingCampaignsCreate: 72,
MarketingCampaignsDelete: 73,
MarketingCampaignsRead: 74,
MarketingCampaignsUpdate: 75,
PartnerSettingsNewRelicRead: 76,
PartnerSettingsNewRelicUpdate: 77,
PartnerSettingsRead: 78,
StatsGlobalRead: 79,
StatsRead: 80,
SubusersCreate: 81,
SubusersCreditsCreate: 82,
SubusersCreditsDelete: 83,
SubusersCreditsRead: 84,
SubusersCreditsRemainingCreate: 85,
SubusersCreditsRemainingDelete: 86,
SubusersCreditsRemainingRead: 87,
SubusersCreditsRemainingUpdate: 88,
SubusersCreditsUpdate: 89,
SubusersDelete: 90,
SubusersMonitorCreate: 91,
SubusersMonitorDelete: 92,
SubusersMonitorRead: 93,
SubusersMonitorUpdate: 94,
SubusersRead: 95,
SubusersReputationsRead: 96,
SubusersStatsMonthlyRead: 97,
SubusersStatsRead: 98,
SubusersStatsSumsRead: 99,
SubusersSummaryRead: 100,
SubusersUpdate: 101,
SuppressionBlocksCreate: 102,
SuppressionBlocksDelete: 103,
SuppressionBlocksRead: 104,
SuppressionBlocksUpdate: 105,
SuppressionBouncesCreate: 106,
SuppressionBouncesDelete: 107,
SuppressionBouncesRead: 108,
SuppressionBouncesUpdate: 109,
SuppressionCreate: 110,
SuppressionDelete: 111,
SuppressionInvalidEmailsCreate: 112,
SuppressionInvalidEmailsDelete: 113,
SuppressionInvalidEmailsRead: 114,
SuppressionInvalidEmailsUpdate: 115,
SuppressionRead: 116,
SuppressionSpamReportsCreate: 117,
SuppressionSpamReportsDelete: 118,
SuppressionSpamReportsRead: 119,
SuppressionSpamReportsUpdate: 120,
SuppressionUnsubscribesCreate: 121,
SuppressionUnsubscribesDelete: 122,
SuppressionUnsubscribesRead: 123,
SuppressionUnsubscribesUpdate: 124,
SuppressionUpdate: 125,
TeammatesCreate: 126,
TeammatesRead: 127,
TeammatesUpdate: 128,
TeammatesDelete: 129,
TemplatesCreate: 130,
TemplatesDelete: 131,
TemplatesRead: 132,
TemplatesUpdate: 133,
TemplatesVersionsActivateCreate: 134,
TemplatesVersionsActivateDelete: 135,
TemplatesVersionsActivateRead: 136,
TemplatesVersionsActivateUpdate: 137,
TemplatesVersionsCreate: 138,
TemplatesVersionsDelete: 139,
TemplatesVersionsRead: 140,
TemplatesVersionsUpdate: 141,
TrackingSettingsClickRead: 142,
TrackingSettingsClickUpdate: 143,
TrackingSettingsGoogleAnalyticsRead: 144,
TrackingSettingsGoogleAnalyticsUpdate: 145,
TrackingSettingsOpenRead: 146,
TrackingSettingsOpenUpdate: 147,
TrackingSettingsRead: 148,
TrackingSettingsSubscriptionRead: 149,
TrackingSettingsSubscriptionUpdate: 150,
UserAccountRead: 151,
UserCreditsRead: 152,
UserEmailCreate: 153,
UserEmailDelete: 154,
UserEmailRead: 155,
UserEmailUpdate: 156,
UserMultifactorAuthenticationCreate: 157,
UserMultifactorAuthenticationDelete: 158,
UserMultifactorAuthenticationRead: 159,
UserMultifactorAuthenticationUpdate: 160,
UserPasswordRead: 161,
UserPasswordUpdate: 162,
UserProfileRead: 163,
UserProfileUpdate: 164,
UserScheduledSendsCreate: 165,
UserScheduledSendsDelete: 166,
UserScheduledSendsRead: 167,
UserScheduledSendsUpdate: 168,
UserSettingsEnforcedTlsRead: 169,
UserSettingsEnforcedTlsUpdate: 170,
UserTimezoneRead: 171,
UserUsernameRead: 172,
UserUsernameUpdate: 173,
UserWebhooksEventSettingsRead: 174,
UserWebhooksEventSettingsUpdate: 175,
UserWebhooksEventTestCreate: 176,
UserWebhooksEventTestRead: 177,
UserWebhooksEventTestUpdate: 178,
UserWebhooksParseSettingsCreate: 179,
UserWebhooksParseSettingsDelete: 180,
UserWebhooksParseSettingsRead: 181,
UserWebhooksParseSettingsUpdate: 182,
UserWebhooksParseStatsRead: 183,
WhitelabelCreate: 184,
WhitelabelDelete: 185,
WhitelabelRead: 186,
WhitelabelUpdate: 187,
}
IdToPermission = map[int]Permission{
1: AccessSettingsActivityRead,
2: AccessSettingsWhitelistCreate,
3: AccessSettingsWhitelistDelete,
4: AccessSettingsWhitelistRead,
5: AccessSettingsWhitelistUpdate,
6: AlertsCreate,
7: AlertsDelete,
8: AlertsRead,
9: AlertsUpdate,
10: ApiKeysCreate,
11: ApiKeysDelete,
12: ApiKeysRead,
13: ApiKeysUpdate,
14: AsmGroupsCreate,
15: AsmGroupsDelete,
16: AsmGroupsRead,
17: AsmGroupsUpdate,
18: BillingCreate,
19: BillingDelete,
20: BillingRead,
21: BillingUpdate,
22: BrowsersStatsRead,
23: CategoriesCreate,
24: CategoriesDelete,
25: CategoriesRead,
26: CategoriesStatsRead,
27: CategoriesStatsSumsRead,
28: CategoriesUpdate,
29: ClientsDesktopStatsRead,
30: ClientsPhoneStatsRead,
31: ClientsStatsRead,
32: ClientsTabletStatsRead,
33: ClientsWebmailStatsRead,
34: DevicesStatsRead,
35: EmailActivityRead,
36: GeoStatsRead,
37: IpsAssignedRead,
38: IpsPoolsCreate,
39: IpsPoolsDelete,
40: IpsPoolsIpsCreate,
41: IpsPoolsIpsDelete,
42: IpsPoolsIpsRead,
43: IpsPoolsIpsUpdate,
44: IpsPoolsRead,
45: IpsPoolsUpdate,
46: IpsRead,
47: IpsWarmupCreate,
48: IpsWarmupDelete,
49: IpsWarmupRead,
50: IpsWarmupUpdate,
51: MailSettingsAddressWhitelistRead,
52: MailSettingsAddressWhitelistUpdate,
53: MailSettingsBouncePurgeRead,
54: MailSettingsBouncePurgeUpdate,
55: MailSettingsFooterRead,
56: MailSettingsFooterUpdate,
57: MailSettingsForwardBounceRead,
58: MailSettingsForwardBounceUpdate,
59: MailSettingsForwardSpamRead,
60: MailSettingsForwardSpamUpdate,
61: MailSettingsPlainContentRead,
62: MailSettingsPlainContentUpdate,
63: MailSettingsRead,
64: MailSettingsTemplateRead,
65: MailSettingsTemplateUpdate,
66: MailBatchCreate,
67: MailBatchDelete,
68: MailBatchRead,
69: MailBatchUpdate,
70: MailSend,
71: MailboxProvidersStatsRead,
72: MarketingCampaignsCreate,
73: MarketingCampaignsDelete,
74: MarketingCampaignsRead,
75: MarketingCampaignsUpdate,
76: PartnerSettingsNewRelicRead,
77: PartnerSettingsNewRelicUpdate,
78: PartnerSettingsRead,
79: StatsGlobalRead,
80: StatsRead,
81: SubusersCreate,
82: SubusersCreditsCreate,
83: SubusersCreditsDelete,
84: SubusersCreditsRead,
85: SubusersCreditsRemainingCreate,
86: SubusersCreditsRemainingDelete,
87: SubusersCreditsRemainingRead,
88: SubusersCreditsRemainingUpdate,
89: SubusersCreditsUpdate,
90: SubusersDelete,
91: SubusersMonitorCreate,
92: SubusersMonitorDelete,
93: SubusersMonitorRead,
94: SubusersMonitorUpdate,
95: SubusersRead,
96: SubusersReputationsRead,
97: SubusersStatsMonthlyRead,
98: SubusersStatsRead,
99: SubusersStatsSumsRead,
100: SubusersSummaryRead,
101: SubusersUpdate,
102: SuppressionBlocksCreate,
103: SuppressionBlocksDelete,
104: SuppressionBlocksRead,
105: SuppressionBlocksUpdate,
106: SuppressionBouncesCreate,
107: SuppressionBouncesDelete,
108: SuppressionBouncesRead,
109: SuppressionBouncesUpdate,
110: SuppressionCreate,
111: SuppressionDelete,
112: SuppressionInvalidEmailsCreate,
113: SuppressionInvalidEmailsDelete,
114: SuppressionInvalidEmailsRead,
115: SuppressionInvalidEmailsUpdate,
116: SuppressionRead,
117: SuppressionSpamReportsCreate,
118: SuppressionSpamReportsDelete,
119: SuppressionSpamReportsRead,
120: SuppressionSpamReportsUpdate,
121: SuppressionUnsubscribesCreate,
122: SuppressionUnsubscribesDelete,
123: SuppressionUnsubscribesRead,
124: SuppressionUnsubscribesUpdate,
125: SuppressionUpdate,
126: TeammatesCreate,
127: TeammatesRead,
128: TeammatesUpdate,
129: TeammatesDelete,
130: TemplatesCreate,
131: TemplatesDelete,
132: TemplatesRead,
133: TemplatesUpdate,
134: TemplatesVersionsActivateCreate,
135: TemplatesVersionsActivateDelete,
136: TemplatesVersionsActivateRead,
137: TemplatesVersionsActivateUpdate,
138: TemplatesVersionsCreate,
139: TemplatesVersionsDelete,
140: TemplatesVersionsRead,
141: TemplatesVersionsUpdate,
142: TrackingSettingsClickRead,
143: TrackingSettingsClickUpdate,
144: TrackingSettingsGoogleAnalyticsRead,
145: TrackingSettingsGoogleAnalyticsUpdate,
146: TrackingSettingsOpenRead,
147: TrackingSettingsOpenUpdate,
148: TrackingSettingsRead,
149: TrackingSettingsSubscriptionRead,
150: TrackingSettingsSubscriptionUpdate,
151: UserAccountRead,
152: UserCreditsRead,
153: UserEmailCreate,
154: UserEmailDelete,
155: UserEmailRead,
156: UserEmailUpdate,
157: UserMultifactorAuthenticationCreate,
158: UserMultifactorAuthenticationDelete,
159: UserMultifactorAuthenticationRead,
160: UserMultifactorAuthenticationUpdate,
161: UserPasswordRead,
162: UserPasswordUpdate,
163: UserProfileRead,
164: UserProfileUpdate,
165: UserScheduledSendsCreate,
166: UserScheduledSendsDelete,
167: UserScheduledSendsRead,
168: UserScheduledSendsUpdate,
169: UserSettingsEnforcedTlsRead,
170: UserSettingsEnforcedTlsUpdate,
171: UserTimezoneRead,
172: UserUsernameRead,
173: UserUsernameUpdate,
174: UserWebhooksEventSettingsRead,
175: UserWebhooksEventSettingsUpdate,
176: UserWebhooksEventTestCreate,
177: UserWebhooksEventTestRead,
178: UserWebhooksEventTestUpdate,
179: UserWebhooksParseSettingsCreate,
180: UserWebhooksParseSettingsDelete,
181: UserWebhooksParseSettingsRead,
182: UserWebhooksParseSettingsUpdate,
183: UserWebhooksParseStatsRead,
184: WhitelabelCreate,
185: WhitelabelDelete,
186: WhitelabelRead,
187: WhitelabelUpdate,
}
)
// ToString converts a Permission enum to its string representation
func (p Permission) ToString() (string, error) {
if str, ok := PermissionStrings[p]; ok {
return str, nil
}
return "", errors.New("invalid permission")
}
// ToID converts a Permission enum to its ID
func (p Permission) ToID() (int, error) {
if id, ok := PermissionIDs[p]; ok {
return id, nil
}
return 0, errors.New("invalid permission")
}
// PermissionFromString converts a string representation to its Permission enum
func PermissionFromString(s string) (Permission, error) {
if p, ok := StringToPermission[s]; ok {
return p, nil
}
return 0, errors.New("invalid permission string")
}
// PermissionFromID converts an ID to its Permission enum
func PermissionFromID(id int) (Permission, error) {
if p, ok := IdToPermission[id]; ok {
return p, nil
}
return 0, errors.New("invalid permission ID")
}

View file

@ -0,0 +1,188 @@
permissions:
- access_settings.activity.read
- access_settings.whitelist.create
- access_settings.whitelist.delete
- access_settings.whitelist.read
- access_settings.whitelist.update
- alerts.create
- alerts.delete
- alerts.read
- alerts.update
- api_keys.create
- api_keys.delete
- api_keys.read
- api_keys.update
- asm.groups.create
- asm.groups.delete
- asm.groups.read
- asm.groups.update
- billing.create
- billing.delete
- billing.read
- billing.update
- browsers.stats.read
- categories.create
- categories.delete
- categories.read
- categories.stats.read
- categories.stats.sums.read
- categories.update
- clients.desktop.stats.read
- clients.phone.stats.read
- clients.stats.read
- clients.tablet.stats.read
- clients.webmail.stats.read
- devices.stats.read
- email_activity.read
- geo.stats.read
- ips.assigned.read
- ips.pools.create
- ips.pools.delete
- ips.pools.ips.create
- ips.pools.ips.delete
- ips.pools.ips.read
- ips.pools.ips.update
- ips.pools.read
- ips.pools.update
- ips.read
- ips.warmup.create
- ips.warmup.delete
- ips.warmup.read
- ips.warmup.update
- mail_settings.address_whitelist.read
- mail_settings.address_whitelist.update
- mail_settings.bounce_purge.read
- mail_settings.bounce_purge.update
- mail_settings.footer.read
- mail_settings.footer.update
- mail_settings.forward_bounce.read
- mail_settings.forward_bounce.update
- mail_settings.forward_spam.read
- mail_settings.forward_spam.update
- mail_settings.plain_content.read
- mail_settings.plain_content.update
- mail_settings.read
- mail_settings.template.read
- mail_settings.template.update
- mail.batch.create
- mail.batch.delete
- mail.batch.read
- mail.batch.update
- mail.send
- mailbox_providers.stats.read
- marketing_campaigns.create
- marketing_campaigns.delete
- marketing_campaigns.read
- marketing_campaigns.update
- partner_settings.new_relic.read
- partner_settings.new_relic.update
- partner_settings.read
- stats.global.read
- stats.read
- subusers.create
- subusers.credits.create
- subusers.credits.delete
- subusers.credits.read
- subusers.credits.remaining.create
- subusers.credits.remaining.delete
- subusers.credits.remaining.read
- subusers.credits.remaining.update
- subusers.credits.update
- subusers.delete
- subusers.monitor.create
- subusers.monitor.delete
- subusers.monitor.read
- subusers.monitor.update
- subusers.read
- subusers.reputations.read
- subusers.stats.monthly.read
- subusers.stats.read
- subusers.stats.sums.read
- subusers.summary.read
- subusers.update
- suppression.blocks.create
- suppression.blocks.delete
- suppression.blocks.read
- suppression.blocks.update
- suppression.bounces.create
- suppression.bounces.delete
- suppression.bounces.read
- suppression.bounces.update
- suppression.create
- suppression.delete
- suppression.invalid_emails.create
- suppression.invalid_emails.delete
- suppression.invalid_emails.read
- suppression.invalid_emails.update
- suppression.read
- suppression.spam_reports.create
- suppression.spam_reports.delete
- suppression.spam_reports.read
- suppression.spam_reports.update
- suppression.unsubscribes.create
- suppression.unsubscribes.delete
- suppression.unsubscribes.read
- suppression.unsubscribes.update
- suppression.update
- teammates.create
- teammates.read
- teammates.update
- teammates.delete
- templates.create
- templates.delete
- templates.read
- templates.update
- templates.versions.activate.create
- templates.versions.activate.delete
- templates.versions.activate.read
- templates.versions.activate.update
- templates.versions.create
- templates.versions.delete
- templates.versions.read
- templates.versions.update
- tracking_settings.click.read
- tracking_settings.click.update
- tracking_settings.google_analytics.read
- tracking_settings.google_analytics.update
- tracking_settings.open.read
- tracking_settings.open.update
- tracking_settings.read
- tracking_settings.subscription.read
- tracking_settings.subscription.update
- user.account.read
- user.credits.read
- user.email.create
- user.email.delete
- user.email.read
- user.email.update
- user.multifactor_authentication.create
- user.multifactor_authentication.delete
- user.multifactor_authentication.read
- user.multifactor_authentication.update
- user.password.read
- user.password.update
- user.profile.read
- user.profile.update
- user.scheduled_sends.create
- user.scheduled_sends.delete
- user.scheduled_sends.read
- user.scheduled_sends.update
- user.settings.enforced_tls.read
- user.settings.enforced_tls.update
- user.timezone.read
- user.username.read
- user.username.update
- user.webhooks.event.settings.read
- user.webhooks.event.settings.update
- user.webhooks.event.test.create
- user.webhooks.event.test.read
- user.webhooks.event.test.update
- user.webhooks.parse.settings.create
- user.webhooks.parse.settings.delete
- user.webhooks.parse.settings.read
- user.webhooks.parse.settings.update
- user.webhooks.parse.stats.read
- whitelabel.create
- whitelabel.delete
- whitelabel.read
- whitelabel.update

View file

@ -1,3 +1,5 @@
//go:generate generate_permissions permissions.yaml permissions.go sendgrid
package sendgrid
import (
@ -13,17 +15,106 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
var _ analyzers.Analyzer = (*Analyzer)(nil)
type Analyzer struct {
Cfg *config.Config
}
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Sendgrid }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
key, ok := credInfo["key"]
if !ok {
return nil, fmt.Errorf("missing key in credInfo")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return secretInfoToAnalyzerResult(info), nil
}
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
var keyType string
if slices.Contains(info.RawScopes, "user.email.read") {
keyType = "full access"
} else if slices.Contains(info.RawScopes, "billing.read") {
keyType = "billing access"
} else {
keyType = "restricted access"
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzerpb.AnalyzerType_Sendgrid,
Metadata: map[string]any{
"key_type": keyType,
"2fa_required": slices.Contains(info.RawScopes, "2fa_required"),
},
Bindings: []analyzers.Binding{},
UnboundedResources: []analyzers.Resource{},
}
for _, scope := range info.Scopes {
resource := getCategoryResource(scope)
if len(scope.Permissions) == 0 {
result.UnboundedResources = append(result.UnboundedResources, *resource)
continue
}
for _, permission := range scope.Permissions {
result.Bindings = append(result.Bindings, analyzers.Binding{
Resource: *resource,
Permission: analyzers.Permission{
Value: permission,
},
})
}
}
return &result
}
func getCategoryResource(scope SendgridScope) *analyzers.Resource {
categoryResource := &analyzers.Resource{
Name: scope.Category,
FullyQualifiedName: scope.Category,
Type: "category",
Metadata: nil,
}
if scope.SubCategory != "" {
return &analyzers.Resource{
Name: scope.SubCategory,
FullyQualifiedName: fmt.Sprintf("%s/%s", scope.Category, scope.SubCategory),
Type: "category",
Metadata: nil,
Parent: categoryResource,
}
}
return categoryResource
}
type ScopesJSON struct {
Scopes []string `json:"scopes"`
}
type SecretInfo struct {
RawScopes []string
Scopes []SendgridScope
}
func printPermissions(show_all bool) {
func printPermissions(info *SecretInfo, show_all bool) {
fmt.Print("\n\n")
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
@ -33,7 +124,7 @@ func printPermissions(show_all bool) {
t.AppendHeader(table.Row{"Scope", "Sub-Scope", "Access"})
}
// Print the scopes
for _, s := range SCOPES {
for _, s := range info.Scopes {
writer := analyzers.GetWriterFromStatus(s.PermissionType)
if show_all {
t.AppendRow([]interface{}{writer(s.Category), writer(s.SubCategory), writer(s.PermissionType), writer(strings.Join(s.Permissions, "\n"))})
@ -49,11 +140,11 @@ func printPermissions(show_all bool) {
// It will return the most specific category possible.
// For example, if the scope is "mail.send.read", it will return "Mail Send", not just "Mail"
// since it's searching "mail.send.read" -> "mail.send" -> "mail"
func getScopeIndex(scope string) int {
func getScopeIndex(categories []SendgridScope, scope string) int {
splitScope := strings.Split(scope, ".")
for i := len(splitScope); i > 0; i-- {
searchScope := strings.Join(splitScope[:i], ".")
for i, s := range SCOPES {
for i, s := range categories {
for _, prefix := range s.Prefixes {
if strings.HasPrefix(searchScope, prefix) {
return i
@ -64,24 +155,36 @@ func getScopeIndex(scope string) int {
return -1
}
func processPermissions(rawScopes []string) {
func processPermissions(rawScopes []string) []SendgridScope {
categoryPermissions := make([]SendgridScope, len(SCOPES))
// copy all scope categories to the categoryPermissions slice
copy(categoryPermissions, SCOPES)
for _, scope := range rawScopes {
// Skip these scopes since they are not useful for this analysis
if scope == "2fa_required" || scope == "sender_verification_eligible" {
continue
}
ind := getScopeIndex(scope)
// must be part of generated permissions
if _, ok := StringToPermission[scope]; !ok {
continue
}
ind := getScopeIndex(categoryPermissions, scope)
if ind == -1 {
//color.Red("[!] Scope not found: %v", scope)
continue
}
s := &SCOPES[ind]
s := &categoryPermissions[ind]
s.AddPermission(scope)
}
// Run tests to determine the permission type
for i := range SCOPES {
SCOPES[i].RunTests()
for i := range categoryPermissions {
categoryPermissions[i].RunTests()
}
return categoryPermissions
}
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
@ -105,7 +208,7 @@ func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
color.Yellow("[i] 2FA Required for this account")
}
printPermissions(cfg.ShowAll)
printPermissions(info, cfg.ShowAll)
}
func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
@ -133,7 +236,10 @@ func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
// Now you can access the scopes
rawScopes := jsonScopes.Scopes
processPermissions(rawScopes)
categoryScope := processPermissions(rawScopes)
return &SecretInfo{RawScopes: rawScopes}, nil
return &SecretInfo{
RawScopes: rawScopes,
Scopes: categoryScope,
}, nil
}

File diff suppressed because one or more lines are too long

View file

@ -55,7 +55,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_ElevenLabs,
Raw: []byte(match),
ExtraData: map[string]string{"version": "1"},
ExtraData: map[string]string{
"version": "1",
"rotation_guide": "https://howtorotate.com/docs/tutorials/elevenlabs/",
},
}
if verify {

View file

@ -46,6 +46,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Eraser,
Raw: []byte(match),
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/eraser/",
},
}
if verify {

View file

@ -42,6 +42,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Groq,
Raw: []byte(match),
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/groq/",
},
}
if verify {

View file

@ -49,7 +49,7 @@ var (
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"lark", "larksuite", "t-", "a-", "u-"}
return []string{"lark", "larksuite"}
}
// FromData will find and optionally verify Larksuite secrets in a given set of bytes.

View file

@ -3,10 +3,11 @@ package mailchimp
import (
"context"
"fmt"
regexp "github.com/wasilibs/go-re2"
"net/http"
"strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@ -63,6 +64,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s.Verified = true
}
}
s.AnalysisInfo = map[string]string{
"key": match,
}
}
results = append(results, s)

View file

@ -93,6 +93,7 @@ func TestMailchimp_FromChunk(t *testing.T) {
t.Fatal("no raw secret present")
}
got[i].Raw = nil
got[i].AnalysisInfo = nil
}
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("Mailchimp.FromData() %s diff: (-got +want)\n%s", tt.name, diff)

View file

@ -51,7 +51,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/ms/",
"rotation_guide": "https://howtorotate.com/docs/tutorials/microsoftteams/",
}
if verify {

View file

@ -87,7 +87,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
} else {
s1.Verified = false
}
s1.AnalysisInfo = map[string]string{
"key": resMatch,
}
}

View file

@ -92,6 +92,7 @@ func TestOpsgenie_FromChunk(t *testing.T) {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
got[i].Raw = nil
got[i].AnalysisInfo = nil
}
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("Opsgenie.FromData() %s diff: (-got +want)\n%s", tt.name, diff)

View file

@ -3,10 +3,11 @@ package postman
import (
"context"
"fmt"
regexp "github.com/wasilibs/go-re2"
"net/http"
"strings"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@ -56,6 +57,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
isVerified, verificationErr := verifyPostman(ctx, client, resMatch)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr, resMatch)
s1.AnalysisInfo = map[string]string{
"key": resMatch,
}
}
results = append(results, s1)

View file

@ -133,7 +133,7 @@ func TestPostman_FromChunk(t *testing.T) {
t.Errorf("Postman.FromData() error = %v, wantErr %v", got[i].VerificationError(), tt.wantVerificationErr)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Postman.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}

View file

@ -0,0 +1,195 @@
package robinhoodcrypto
import (
"context"
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
regexp "github.com/wasilibs/go-re2"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
// Reference: https://docs.robinhood.com/crypto/trading/#section/Authentication
keyPat = 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`)
// Matches base64 strings. Taken from https://stackoverflow.com/a/475217.
privKeyBase64Pat = regexp.MustCompile(`(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"rh-api-"}
}
// FromData will find and optionally verify RobinhoodCrypto secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
apiKeyMatches := make(map[string]struct{})
for _, match := range keyPat.FindAllStringSubmatch(dataStr, -1) {
apiKeyMatches[match[1]] = struct{}{}
}
base64PrivateKeyMatches := make(map[string]struct{})
for _, match := range privKeyBase64Pat.FindAllString(dataStr, -1) {
base64PrivateKeyMatches[match] = struct{}{}
}
for apiKey := range apiKeyMatches {
for base64PrivateKey := range base64PrivateKeyMatches {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
Raw: []byte(apiKey),
RawV2: []byte(apiKey + base64PrivateKey),
}
if verify {
client := s.client
if client == nil {
client = defaultClient
}
isVerified, extraData, verificationErr := verifyMatch(ctx, client, apiKey, base64PrivateKey)
s1.Verified = isVerified
s1.ExtraData = extraData
s1.SetVerificationError(verificationErr, apiKey, base64PrivateKey)
}
results = append(results, s1)
}
}
return
}
func verifyMatch(ctx context.Context, client *http.Client, apiKey, base64PrivateKey string) (
bool, map[string]string, error,
) {
// Decode the base64 private key.
privateBytes, err := base64.StdEncoding.DecodeString(base64PrivateKey)
if err != nil {
return false, nil, fmt.Errorf("failed to decode base64 private key: %w", err)
}
// Sanity check the private key length.
if len(privateBytes) < 32 {
return false, nil, fmt.Errorf("private key is too short, expected at least 32 bytes, got %d", len(privateBytes))
}
// Create the private key from the seed.
privateKey := ed25519.NewKeyFromSeed(privateBytes[:32])
// Draft the message to be signed.
// Reference: https://docs.robinhood.com/crypto/trading/#section/Authentication/Headers-and-Signature
var (
timestamp = fmt.Sprint(time.Now().UTC().Unix())
path = "/api/v1/crypto/trading/accounts/"
method = http.MethodGet
body = ""
)
message := apiKey + timestamp + path + method + body
signature := ed25519.Sign(privateKey, []byte(message))
req, err := http.NewRequestWithContext(ctx, method, "https://trading.robinhood.com/"+path, strings.NewReader(body))
if err != nil {
return false, nil, nil
}
// Set the required headers.
headers := map[string]string{
"x-api-key": apiKey,
"x-signature": base64.StdEncoding.EncodeToString(signature),
"x-timestamp": timestamp,
}
for key, value := range headers {
req.Header.Add(key, value)
}
res, err := client.Do(req)
if err != nil {
return false, nil, err
}
defer func() {
_, _ = io.Copy(io.Discard, res.Body)
_ = res.Body.Close()
}()
switch res.StatusCode {
// StatusOK: The secret is verified.
case http.StatusOK:
// Include the additional information returned by the endpoint.
if len(res.Header) > 0 && res.Header.Get("Content-Type") == "application/json" {
response := struct {
AccountNumber string `json:"account_number"`
Status string `json:"status"`
BuyingPower string `json:"buying_power"`
BuyingPowerCurrency string `json:"buying_power_currency"`
}{}
if err = json.NewDecoder(res.Body).Decode(&response); err != nil {
return true, nil, fmt.Errorf("failed to obtain additional information: %w", err)
}
return true, map[string]string{"Robinhood Crypto Account Number": response.AccountNumber}, nil
}
// The secret is verified, but there is no additional information.
return true, nil, nil
// StatusForbidden: The secret is valid, but the credentials do not have access to the endpoint.
case http.StatusForbidden:
return true, map[string]string{"Explanation": "Valid credentials without access to Get Crypto Trading Account Details API"}, nil
// StatusUnauthorized:
// Two scenarios can happen,
// 1. The secret is verified, but is currently inactive.
// 2. The secret is determinately not verified.
case http.StatusUnauthorized:
// Check if the secret is verified but currently inactive.
// We want to handle this case because an inactive secret can be activated in the future, at which point it
// becomes a security risk.
if len(res.Header) > 0 && res.Header.Get("Content-Type") == "text/plain" {
body, err := io.ReadAll(res.Body)
if err != nil {
// The secret is considered verified but inactive only if the body suggests so. Since the body is not
// readable, we cannot determine if the secret is verified but inactive.
return false, nil, fmt.Errorf("failed to read response body: %w", err)
}
if strings.TrimSpace(string(body)) == "API credential is not active." {
return true, map[string]string{"Explanation": "Valid credentials in inactive state"}, nil
}
}
// The secret is determinately not verified (nothing to do)
return false, nil, nil
default:
return false, nil, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_RobinhoodCrypto
}

View file

@ -0,0 +1,274 @@
//go:build detectors
// +build detectors
package robinhoodcrypto
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestRobinhoodCrypto_Pattern(t *testing.T) {
d := Scanner{}
ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d})
tests := []struct {
name string
input string
want []string
}{
{
name: "typical pattern",
input: `
api_key = "rh-api-e3bb245e-a45c-4729-8a9b-10201756f8cc"
private_key_base64 = "aVhXn8ghC9YqSz5RyFuKc6SsDC6SuPIqSW3IXH76ZlMCjOxkazBQjQFucJLk3uNorpBt6TbYpo/D1lHA7s4+hQ=="
`,
want: []string{
"rh-api-e3bb245e-a45c-4729-8a9b-10201756f8cc" +
"aVhXn8ghC9YqSz5RyFuKc6SsDC6SuPIqSW3IXH76ZlMCjOxkazBQjQFucJLk3uNorpBt6TbYpo/D1lHA7s4+hQ==",
},
},
}
for _, test := range tests {
t.Run(
test.name, func(t *testing.T) {
matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input))
if len(matchedDetectors) == 0 {
t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input)
return
}
results, err := d.FromData(context.Background(), false, []byte(test.input))
if err != nil {
t.Errorf("error = %v", err)
return
}
if len(results) != len(test.want) {
if len(results) == 0 {
t.Errorf("did not receive result")
} else {
t.Errorf("expected %d results, only received %d", len(test.want), len(results))
}
return
}
actual := make(map[string]struct{}, len(results))
for _, r := range results {
if len(r.RawV2) > 0 {
actual[string(r.RawV2)] = struct{}{}
} else {
actual[string(r.Raw)] = struct{}{}
}
}
expected := make(map[string]struct{}, len(test.want))
for _, v := range test.want {
expected[v] = struct{}{}
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Errorf("%s diff: (-want +got)\n%s", test.name, diff)
}
},
)
}
}
func TestRobinhoodcrypto_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
// Valid and active credentials.
apiKey := testSecrets.MustGetField("ROBINHOODCRYPTO_APIKEY")
privateKey := testSecrets.MustGetField("ROBINHOODCRYPTO_PRIVATEKEY")
// Valid but inactive credentials.
inactiveApiKey := testSecrets.MustGetField("ROBINHOODCRYPTO_APIKEY_INACTIVE")
inactivePrivateKey := testSecrets.MustGetField("ROBINHOODCRYPTO_PRIVATEKEY_INACTIVE")
// Invalid credentials.
deletedApiKey := testSecrets.MustGetField("ROBINHOODCRYPTO_APIKEY_DELETED")
deletedPrivateKey := testSecrets.MustGetField("ROBINHOODCRYPTO_PRIVATEKEY_DELETED")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(
"You can find a robinhoodcrypto api key %s and a private key %s within", apiKey, privateKey,
)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, verified, but inactive",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(
"You can find a robinhoodcrypto api key %s and a private key %s within", inactiveApiKey,
inactivePrivateKey,
)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(
"You can find a robinhoodcrypto api key %s and a private key %s within", deletedApiKey,
deletedPrivateKey,
)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, would be verified if not for timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(
"You can find a robinhoodcrypto api key %s and a private key %s within", apiKey, privateKey,
)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
{
name: "found, verified but unexpected api surface",
s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf(
"You can find a robinhoodcrypto api key %s and a private key %s within", apiKey, privateKey,
)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_RobinhoodCrypto,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
}
for _, tt := range tests {
t.Run(
tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Robinhoodcrypto.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
t.Fatalf(
"wantVerificationError = %v, verification error = %v", tt.wantVerificationErr,
got[i].VerificationError(),
)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "ExtraData", "Raw", "RawV2", "verificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Robinhoodcrypto.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
},
)
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(
name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
},
)
}
}

View file

@ -60,6 +60,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1.Verified = verified
s1.ExtraData = extraData
s1.SetVerificationError(verificationErr)
s1.AnalysisInfo = map[string]string{"key": token}
}
results = append(results, s1)

View file

@ -126,7 +126,7 @@ func TestSendgrid_FromChunk(t *testing.T) {
t.Errorf("Sendgrid.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo", "ExtraData")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Sendgrid.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}

View file

@ -577,6 +577,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ringcentral"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ritekit"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/roaring"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/robinhoodcrypto"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rocketreach"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/rockset"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/roninapp"
@ -1626,6 +1627,7 @@ func DefaultDetectors() []detectors.Detector {
atlassianv1.Scanner{},
atlassianv2.Scanner{},
netsuite.Scanner{},
robinhoodcrypto.Scanner{},
}
}

View file

@ -135,6 +135,15 @@ type Config struct {
// that have been detected by multiple detectors.
// By default, it is set to true.
VerificationOverlap bool
// DetectorWorkerMultiplier is used to determine the number of detector workers to spawn.
DetectorWorkerMultiplier int
// NotificationWorkerMultiplier is used to determine the number of notification workers to spawn.
NotificationWorkerMultiplier int
// VerificationOverlapWorkerMultiplier is used to determine the number of verification overlap workers to spawn.
VerificationOverlapWorkerMultiplier int
}
// Engine represents the core scanning engine responsible for detecting secrets in input data.
@ -195,24 +204,34 @@ type Engine struct {
// Note: bad hack only used for testing.
verificationOverlapTracker *verificationOverlapTracker
// detectorWorkerMultiplier is used to calculate the number of detector workers.
detectorWorkerMultiplier int
// notificationWorkerMultiplier is used to calculate the number of notification workers.
notificationWorkerMultiplier int
// verificationOverlapWorkerMultiplier is used to calculate the number of verification overlap workers.
verificationOverlapWorkerMultiplier int
}
// NewEngine creates a new Engine instance with the provided configuration.
func NewEngine(ctx context.Context, cfg *Config) (*Engine, error) {
engine := &Engine{
concurrency: cfg.Concurrency,
decoders: cfg.Decoders,
detectors: cfg.Detectors,
dispatcher: cfg.Dispatcher,
verify: cfg.Verify,
filterUnverified: cfg.FilterUnverified,
filterEntropy: cfg.FilterEntropy,
printAvgDetectorTime: cfg.PrintAvgDetectorTime,
retainFalsePositives: cfg.LogFilteredUnverified,
verificationOverlap: cfg.VerificationOverlap,
sourceManager: cfg.SourceManager,
scanEntireChunk: cfg.ShouldScanEntireChunk,
detectorVerificationOverrides: cfg.DetectorVerificationOverrides,
concurrency: cfg.Concurrency,
decoders: cfg.Decoders,
detectors: cfg.Detectors,
dispatcher: cfg.Dispatcher,
verify: cfg.Verify,
filterUnverified: cfg.FilterUnverified,
filterEntropy: cfg.FilterEntropy,
printAvgDetectorTime: cfg.PrintAvgDetectorTime,
retainFalsePositives: cfg.LogFilteredUnverified,
verificationOverlap: cfg.VerificationOverlap,
sourceManager: cfg.SourceManager,
scanEntireChunk: cfg.ShouldScanEntireChunk,
detectorVerificationOverrides: cfg.DetectorVerificationOverrides,
detectorWorkerMultiplier: cfg.DetectorWorkerMultiplier,
notificationWorkerMultiplier: cfg.NotificationWorkerMultiplier,
verificationOverlapWorkerMultiplier: cfg.VerificationOverlapWorkerMultiplier,
}
if engine.sourceManager == nil {
return nil, fmt.Errorf("source manager is required")
@ -303,7 +322,19 @@ func (e *Engine) setDefaults(ctx context.Context) {
ctx.Logger().Info("No concurrency specified, defaulting to max", "cpu", numCPU)
e.concurrency = numCPU
}
ctx.Logger().V(3).Info("engine started", "workers", e.concurrency)
if e.detectorWorkerMultiplier < 1 {
// bound by net i/o so it's higher than other workers
e.detectorWorkerMultiplier = 8
}
if e.notificationWorkerMultiplier < 1 {
e.notificationWorkerMultiplier = 1
}
if e.verificationOverlapWorkerMultiplier < 1 {
e.verificationOverlapWorkerMultiplier = 1
}
// Default decoders handle common encoding formats.
if len(e.decoders) == 0 {
@ -457,6 +488,7 @@ func (e *Engine) initialize(ctx context.Context) error {
// This reflects the anticipated lower volume of data that needs re-verification.
// The buffer size is a trade-off between memory usage and the need to prevent blocking.
verificationOverlapChunksChanMultiplier = 25
resultsChanMultiplier = detectableChunksChanMultiplier
)
// Channels are used for communication between different parts of the engine,
@ -467,7 +499,7 @@ func (e *Engine) initialize(ctx context.Context) error {
e.verificationOverlapChunksChan = make(
chan verificationOverlapChunk, defaultChannelBuffer*verificationOverlapChunksChanMultiplier,
)
e.results = make(chan detectors.ResultWithMetadata, defaultChannelBuffer)
e.results = make(chan detectors.ResultWithMetadata, defaultChannelBuffer*resultsChanMultiplier)
e.dedupeCache = cache
ctx.Logger().V(4).Info("engine initialized")
@ -624,9 +656,10 @@ func (e *Engine) startScannerWorkers(ctx context.Context) {
}
func (e *Engine) startDetectorWorkers(ctx context.Context) {
const detectorWorkerMultiplier = 4
ctx.Logger().V(2).Info("starting detector workers", "count", e.concurrency*detectorWorkerMultiplier)
for worker := uint64(0); worker < uint64(e.concurrency*detectorWorkerMultiplier); worker++ {
numWorkers := e.concurrency * e.detectorWorkerMultiplier
ctx.Logger().V(2).Info("starting detector workers", "count", numWorkers)
for worker := 0; worker < numWorkers; worker++ {
e.wgDetectorWorkers.Add(1)
go func() {
ctx := context.WithValue(ctx, "detector_worker_id", common.RandomID(5))
@ -638,8 +671,10 @@ func (e *Engine) startDetectorWorkers(ctx context.Context) {
}
func (e *Engine) startVerificationOverlapWorkers(ctx context.Context) {
ctx.Logger().V(2).Info("starting verificationOverlap workers", "count", e.concurrency)
for worker := uint64(0); worker < uint64(e.concurrency); worker++ {
numWorkers := e.concurrency * e.verificationOverlapWorkerMultiplier
ctx.Logger().V(2).Info("starting verificationOverlap workers", "count", numWorkers)
for worker := 0; worker < numWorkers; worker++ {
e.verificationOverlapWg.Add(1)
go func() {
ctx := context.WithValue(ctx, "verification_overlap_worker_id", common.RandomID(5))
@ -651,13 +686,10 @@ func (e *Engine) startVerificationOverlapWorkers(ctx context.Context) {
}
func (e *Engine) startNotifierWorkers(ctx context.Context) {
const notifierWorkerRatio = 4
maxNotifierWorkers := 1
if numWorkers := e.concurrency / notifierWorkerRatio; numWorkers > 0 {
maxNotifierWorkers = numWorkers
}
ctx.Logger().V(2).Info("starting notifier workers", "count", maxNotifierWorkers)
for worker := 0; worker < maxNotifierWorkers; worker++ {
numWorkers := e.notificationWorkerMultiplier * e.concurrency
ctx.Logger().V(2).Info("starting notifier workers", "count", numWorkers)
for worker := 0; worker < numWorkers; worker++ {
e.WgNotifier.Add(1)
go func() {
ctx := context.WithValue(ctx, "notifier_worker_id", common.RandomID(5))

View file

@ -26,17 +26,17 @@ func (e *Engine) ScanPostman(ctx context.Context, c sources.PostmanConfig) error
CollectionPaths: c.CollectionPaths,
EnvironmentPaths: c.EnvironmentPaths,
}
// Check if postman data is going to be accessed via an api call using a token, or
// if it has been already exported and exists locally
if len(c.Token) > 0 {
connection.Credential = &sourcespb.Postman_Token{
Token: c.Token,
}
} else {
} else if len(c.WorkspacePaths) > 0 || len(c.CollectionPaths) > 0 || len(c.EnvironmentPaths) > 0 {
connection.Credential = &sourcespb.Postman_Unauthenticated{}
}
if len(c.Workspaces) == 0 && len(c.Collections) == 0 && len(c.Environments) == 0 && len(c.Token) == 0 && len(c.WorkspacePaths) == 0 && len(c.CollectionPaths) == 0 && len(c.EnvironmentPaths) == 0 {
ctx.Logger().Error(errors.New("no postman workspaces, collections, environments or API token provided"), "failed to scan postman")
return nil
} else {
return errors.New("no path to locally exported data or API token provided")
}
// Turn AhoCorasick keywordsToDetectors into a map of keywords

View file

@ -0,0 +1,77 @@
package engine
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/decoders"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
)
func TestPostmanEngine(t *testing.T) {
tests := []struct {
name string
postmanConfig sources.PostmanConfig
wantErr bool
}{
{
name: "scanned Postman with a token",
postmanConfig: sources.PostmanConfig{
Token: "dummy_key",
},
},
{
name: "scanned Postman with workspacePath",
postmanConfig: sources.PostmanConfig{
WorkspacePaths: []string{"Downloads/Test API.postman_collection.json"},
},
},
{
name: "scanned Postman with environmentPath",
postmanConfig: sources.PostmanConfig{
EnvironmentPaths: []string{"Downloads/Mobile - Points Unlock Redeemables.postman_environment.json"},
},
},
{
name: "no token or file path provided",
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx, cancel := context.WithCancel(context.TODO())
defer cancel()
const defaultOutputBufferSize = 64
opts := []func(*sources.SourceManager){
sources.WithSourceUnits(),
sources.WithBufferedOutput(defaultOutputBufferSize),
}
sourceManager := sources.NewManager(opts...)
conf := Config{
Concurrency: 1,
Decoders: decoders.DefaultDecoders(),
Detectors: DefaultDetectors(),
Verify: false,
SourceManager: sourceManager,
Dispatcher: NewPrinterDispatcher(new(discardPrinter)),
}
e, err := NewEngine(ctx, &conf)
assert.NoError(t, err)
e.Start(ctx)
err = e.ScanPostman(ctx, test.postmanConfig)
if err != nil && !test.wantErr {
t.Errorf("ScanPostman() got: %v, want: %v", err, nil)
return
}
if err == nil && test.wantErr {
t.Errorf("ScanPostman() got: %v, want: %v", err, "error")
}
})
}
}

8
pkg/feature/feature.go Normal file
View file

@ -0,0 +1,8 @@
package feature
import "sync/atomic"
var (
ForceSkipBinaries = atomic.Bool{}
ForceSkipArchives = atomic.Bool{}
)

View file

@ -9,6 +9,7 @@ import (
"pault.ag/go/debian/deb"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
)
// arHandler handles AR archive formats.
@ -24,6 +25,11 @@ func newARHandler() *arHandler {
func (h *arHandler) HandleFile(ctx logContext.Context, input fileReader) (chan []byte, error) {
archiveChan := make(chan []byte, defaultBufferSize)
if feature.ForceSkipArchives.Load() {
close(archiveChan)
return archiveChan, nil
}
go func() {
ctx, cancel := logContext.WithTimeout(ctx, maxTimeout)
defer cancel()

View file

@ -10,6 +10,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
)
type ctxKey int
@ -50,6 +51,11 @@ func newArchiveHandler() *archiveHandler {
func (h *archiveHandler) HandleFile(ctx logContext.Context, input fileReader) (chan []byte, error) {
dataChan := make(chan []byte, defaultBufferSize)
if feature.ForceSkipArchives.Load() {
close(dataChan)
return dataChan, nil
}
go func() {
ctx, cancel := logContext.WithTimeout(ctx, maxTimeout)
defer cancel()

View file

@ -9,6 +9,7 @@ import (
"github.com/sassoftware/go-rpmutils"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
)
// rpmHandler specializes archiveHandler to manage RPM package files.
@ -24,6 +25,11 @@ func newRPMHandler() *rpmHandler {
func (h *rpmHandler) HandleFile(ctx logContext.Context, input fileReader) (chan []byte, error) {
archiveChan := make(chan []byte, defaultBufferSize)
if feature.ForceSkipArchives.Load() {
close(archiveChan)
return archiveChan, nil
}
go func() {
ctx, cancel := logContext.WithTimeout(ctx, maxTimeout)
defer cancel()

View file

@ -1097,6 +1097,7 @@ const (
DetectorType_EndorLabs DetectorType = 993
DetectorType_ElevenLabs DetectorType = 994
DetectorType_Netsuite DetectorType = 995
DetectorType_RobinhoodCrypto DetectorType = 996
)
// Enum value maps for DetectorType.
@ -2094,6 +2095,7 @@ var (
993: "EndorLabs",
994: "ElevenLabs",
995: "Netsuite",
996: "RobinhoodCrypto",
}
DetectorType_value = map[string]int32{
"Alibaba": 0,
@ -3088,6 +3090,7 @@ var (
"EndorLabs": 993,
"ElevenLabs": 994,
"Netsuite": 995,
"RobinhoodCrypto": 996,
}
)
@ -3541,7 +3544,7 @@ var file_detectors_proto_rawDesc = []byte{
0x4c, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x41, 0x53, 0x45, 0x36, 0x34,
0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x12, 0x13, 0x0a,
0x0f, 0x45, 0x53, 0x43, 0x41, 0x50, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45,
0x10, 0x04, 0x2a, 0x98, 0x7f, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54,
0x10, 0x04, 0x2a, 0xae, 0x7f, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54,
0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x10, 0x00,
0x12, 0x08, 0x0a, 0x04, 0x41, 0x4d, 0x51, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57,
0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x10, 0x03, 0x12, 0x0a,
@ -4558,12 +4561,13 @@ var file_detectors_proto_rawDesc = []byte{
0x75, 0x69, 0x74, 0x65, 0x41, 0x70, 0x69, 0x4b, 0x65, 0x79, 0x10, 0xe0, 0x07, 0x12, 0x0e, 0x0a,
0x09, 0x45, 0x6e, 0x64, 0x6f, 0x72, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xe1, 0x07, 0x12, 0x0f, 0x0a,
0x0a, 0x45, 0x6c, 0x65, 0x76, 0x65, 0x6e, 0x4c, 0x61, 0x62, 0x73, 0x10, 0xe2, 0x07, 0x12, 0x0d,
0x0a, 0x08, 0x4e, 0x65, 0x74, 0x73, 0x75, 0x69, 0x74, 0x65, 0x10, 0xe3, 0x07, 0x42, 0x3d, 0x5a,
0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66,
0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66,
0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62,
0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x0a, 0x08, 0x4e, 0x65, 0x74, 0x73, 0x75, 0x69, 0x74, 0x65, 0x10, 0xe3, 0x07, 0x12, 0x14, 0x0a,
0x0f, 0x52, 0x6f, 0x62, 0x69, 0x6e, 0x68, 0x6f, 0x6f, 0x64, 0x43, 0x72, 0x79, 0x70, 0x74, 0x6f,
0x10, 0xe4, 0x07, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74,
0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f,
0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View file

@ -28,6 +28,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/cleantemp"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/feature"
"github.com/trufflesecurity/trufflehog/v3/pkg/gitparse"
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
@ -1232,7 +1233,7 @@ func (s *Git) handleBinary(ctx context.Context, gitDir string, reporter sources.
return nil
}
if s.skipBinaries {
if s.skipBinaries || feature.ForceSkipBinaries.Load() {
fileCtx.Logger().V(5).Info("skipping binary file", "path", path)
return nil
}

View file

@ -16,6 +16,7 @@ import (
"github.com/go-logr/logr"
"github.com/gobwas/glob"
"github.com/google/go-github/v63/github"
"github.com/trufflesecurity/trufflehog/v3/pkg/handlers"
"golang.org/x/exp/rand"
"golang.org/x/sync/errgroup"
"google.golang.org/protobuf/proto"
@ -1392,35 +1393,34 @@ func (s *Source) scanTarget(ctx context.Context, target sources.ChunkingTarget,
return fmt.Errorf("invalid GitHub URL")
}
qry := commitQuery{
repo: segments[2],
owner: segments[1],
sha: meta.GetCommit(),
filename: meta.GetFile(),
readCloser, resp, err := s.connector.APIClient().Repositories.DownloadContents(
ctx,
segments[1],
segments[2],
meta.GetFile(),
&github.RepositoryContentGetOptions{Ref: meta.GetCommit()})
// As of this writing, if the returned readCloser is not nil, it's just the Body of the returned github.Response, so
// there's no need to independently close it.
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
res, err := s.getDiffForFileInCommit(ctx, qry)
if err != nil {
return err
return fmt.Errorf("could not download file for scan: %w", err)
}
chunk := &sources.Chunk{
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected HTTP response status when trying to download file for scan: %v", resp.Status)
}
reporter := sources.ChanReporter{Ch: chunksChan}
chunkSkel := sources.Chunk{
SourceType: s.Type(),
SourceName: s.name,
SourceID: s.SourceID(),
JobID: s.JobID(),
SecretID: target.SecretID,
Data: []byte(stripLeadingPlusMinus(res)),
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{Github: meta},
},
Verify: s.verify,
}
return common.CancellableWrite(ctx, chunksChan, chunk)
}
// stripLeadingPlusMinus removes leading + and - characters from lines in a diff string. These characters exist in the
// diffs returned when performing a targeted scan and need to be removed so that detectors are operating on the correct
// text.
func stripLeadingPlusMinus(diff string) string {
return strings.ReplaceAll(strings.ReplaceAll(diff, "\n+", "\n"), "\n-", "\n")
Verify: s.verify}
return handlers.HandleFile(ctx, readCloser, &chunkSkel, reporter)
}

View file

@ -758,6 +758,24 @@ func TestSource_Chunks_TargetedScan(t *testing.T) {
},
wantChunks: 1,
},
{
name: "targeted scan, binary file",
init: init{
name: "test source",
connection: &sourcespb.GitHub{Credential: &sourcespb.GitHub_Token{Token: githubToken}},
queryCriteria: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_Github{
Github: &source_metadatapb.Github{
Repository: "https://github.com/truffle-sandbox/test-secrets.git",
Link: "https://github.com/truffle-sandbox/test-secrets/blob/70bef8590f87257c0992eecc7db529827a12b801/null_text_w_ptp.ipynb",
Commit: "70bef8590f87257c0992eecc7db529827a12b801",
File: "null_text_w_ptp.ipynb",
},
},
},
},
wantChunks: 607,
},
{
name: "no file in commit",
init: init{

View file

@ -281,60 +281,6 @@ func (s *Source) wikiIsReachable(ctx context.Context, repoURL string) bool {
return wikiURL == res.Request.URL.String()
}
// commitQuery represents the details required to fetch a commit.
type commitQuery struct {
repo string
owner string
sha string
filename string
}
// getDiffForFileInCommit retrieves the diff for a specified file in a commit.
// If the file or its diff is not found, it returns an error.
func (s *Source) getDiffForFileInCommit(ctx context.Context, query commitQuery) (string, error) {
var (
commit *github.RepositoryCommit
err error
)
for {
commit, _, err = s.connector.APIClient().Repositories.GetCommit(ctx, query.owner, query.repo, query.sha, nil)
if s.handleRateLimit(err) {
continue
}
if err != nil {
return "", fmt.Errorf("error fetching commit %s: %w", query.sha, err)
}
break
}
if len(commit.Files) == 0 {
return "", fmt.Errorf("commit %s does not contain any files", query.sha)
}
res := new(strings.Builder)
// Only return the diff if the file is in the commit.
for _, file := range commit.Files {
if *file.Filename != query.filename {
continue
}
if file.Patch == nil {
return "", fmt.Errorf("commit %s file %s does not have a diff", query.sha, query.filename)
}
if _, err := res.WriteString(*file.Patch); err != nil {
return "", fmt.Errorf("buffer write error for commit %s file %s: %w", query.sha, query.filename, err)
}
res.WriteString("\n")
}
if res.Len() == 0 {
return "", fmt.Errorf("commit %s does not contain patch for file %s", query.sha, query.filename)
}
return res.String(), nil
}
func (s *Source) normalizeRepo(repo string) (string, error) {
// If there's a '/', assume it's a URL and try to normalize it.
if strings.ContainsRune(repo, '/') {

View file

@ -65,6 +65,7 @@ func New(c common.Common) *SourceSelect {
OssItem("Git", "Scan git repositories."),
OssItem("GitHub", "Scan GitHub repositories and/or organizations."),
OssItem("Filesystem", "Scan your filesystem by selecting what directories to scan."),
OssItem("Hugging Face", "Scan Hugging Face, an AI/ML community."),
OssItem("Jenkins", "Scan Jenkins, a CI/CD platform. (Recently open-sourced from enterprise!)"),
OssItem("Elasticsearch", "Scan your Elasticsearch cluster or Elastic Cloud instance."),
OssItem("Postman", "Scan a collection, workspace, or environment from Postman, the API platform."),

View file

@ -0,0 +1,77 @@
package huggingface
import (
"strings"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
)
type huggingFaceCmdModel struct {
textinputs.Model
}
func GetNote() string {
return "Please enter the organization, user, model, space, or dataset you would like to scan."
}
func GetFields() huggingFaceCmdModel {
org := textinputs.InputConfig{
Label: "Organization",
Key: "org",
Required: false,
Help: "Hugging Face organization name. This will scan all models, datasets, and spaces belonging to the organization.",
}
user := textinputs.InputConfig{
Label: "Username",
Key: "user",
Required: false,
Help: "Hugging Face user. This will scan all models, datasets, and spaces belonging to the user.",
}
model := textinputs.InputConfig{
Label: "Model",
Key: "model",
Required: false,
Help: "Hugging Face model. Example: org/model_name or user/model_name",
}
space := textinputs.InputConfig{
Label: "Space",
Key: "space",
Required: false,
Help: "Hugging Face space. Example: org/space_name or user/space_name.",
}
dataset := textinputs.InputConfig{
Label: "Dataset",
Key: "dataset",
Required: false,
Help: "Hugging Face dataset. Example: org/dataset_name or user/dataset_name.",
}
return huggingFaceCmdModel{textinputs.New([]textinputs.InputConfig{org, user, model, space, dataset})}
}
func (m huggingFaceCmdModel) Cmd() string {
var command []string
command = append(command, "trufflehog", "huggingface")
inputs := m.GetInputs()
keys := []string{"org", "user", "model", "space", "dataset"}
for _, key := range keys {
val, ok := inputs[key]
if !ok || val.Value == "" {
continue
}
command = append(command, "--"+key+"="+val.Value)
}
return strings.Join(command, " ")
}
func (m huggingFaceCmdModel) Summary() string {
inputs := m.GetInputs()
labels := m.GetLabels()
keys := []string{"org", "user", "model", "space", "dataset"}
return common.SummarizeSource(keys, inputs, labels)
}

View file

@ -12,6 +12,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/git"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/github"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/gitlab"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/huggingface"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/jenkins"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/postman"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/sources/s3"
@ -27,6 +28,8 @@ func GetSourceNotes(sourceName string) string {
return postman.GetNote()
case "elasticsearch":
return elasticsearch.GetNote()
case "huggingface":
return huggingface.GetNote()
case "jenkins":
return jenkins.GetNote()
@ -63,6 +66,8 @@ func GetSourceFields(sourceName string) CmdModel {
return github.GetFields()
case "gitlab":
return gitlab.GetFields()
case "hugging face":
return huggingface.GetFields()
case "jenkins":
return jenkins.GetFields()
case "postman":

View file

@ -1005,6 +1005,7 @@ enum DetectorType {
EndorLabs = 993;
ElevenLabs = 994;
Netsuite = 995;
RobinhoodCrypto = 996;
}
message Result {

View file

@ -331,6 +331,18 @@ message Elasticsearch {
string timestamp = 3;
}
message Sentry {
string event_id = 1;
string organization_id = 2;
string organization_slug = 3;
string organization_date_created = 4;
string project_id = 5;
string project_slug = 6;
string issue_id = 7;
string date_created = 8;
string link = 9;
}
message MetaData {
oneof data {
Azure azure = 1;
@ -365,5 +377,6 @@ message MetaData {
Webhook webhook = 30;
Elasticsearch elasticsearch = 31;
Huggingface huggingface = 32;
Sentry sentry = 33;
}
}

View file

@ -50,6 +50,7 @@ enum SourceType {
SOURCE_TYPE_ELASTICSEARCH = 35;
SOURCE_TYPE_HUGGINGFACE = 36;
SOURCE_TYPE_GITHUB_EXPERIMENTAL = 37;
SOURCE_TYPE_SENTRY = 38;
}
message LocalSource {
@ -458,3 +459,14 @@ message Elasticsearch {
string since_timestamp = 9;
bool best_effort_scan = 10;
}
message Sentry {
string endpoint = 1;
oneof credential {
string auth_token = 2;
string dsn_key = 3;
string api_key = 4;
}
bool insecure_skip_verify_tls = 5;
string projects = 6;
}