Compare commits

...

30 Commits

Author SHA1 Message Date
Dustin Decker 065984ee58 review updates 2023-10-18 09:05:07 -07:00
Miccah Castorina fe90effec1 Continue chunking on error 2023-10-16 16:42:33 -07:00
Miccah Castorina e5a34a9809 Remove jobPage loop
ListByBuild does not support pagination, so this was infinitely
repeating. https://developer.travis-ci.com/resource/jobs#find
2023-10-16 16:21:56 -07:00
Dustin Decker ec3c59d025 update test to use sourcestest 2023-10-16 14:00:22 -07:00
Dustin Decker c99e0b93f7 Merge branch 'main' of github.com:trufflesecurity/trufflehog into travis-scanning 2023-10-16 13:42:35 -07:00
Miccah 03dc7cb68d [chore] Add SourceUnitEnumChunker filesystem tests (#1873)
* [chore] Add SourceUnitEnumChunker filesystem tests

* Ensure reported units are exactly what is expected
2023-10-16 10:42:18 -07:00
Corben Leo 072e1f9dcf Detector-Competition-Fix: Add Personal Access Tokens (API Tokens Depr… (#1871)
* Detector-Competition-Fix: Add Personal Access Tokens (API Tokens Depreciation)

* fix(test): fix test debug msg

* remove print
2023-10-16 08:17:12 -05:00
ahrav 5c721d1a73 [bug] - Don't modify global client var (#1890)
* Create a new client within the verify block

* remove unused var
2023-10-13 12:32:21 -07:00
s.shivasurya 040167178c added cody gateway token detection code (#1883)
* added cody gateway token detection code

* resolved conflict
2023-10-13 09:09:04 -06:00
dependabot[bot] 6f00541205 Bump golang.org/x/net from 0.15.0 to 0.17.0 (#1886)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-11 17:26:00 -06:00
Corben Leo ae3a5d1202 Detector-Competition-Feat: Add Klaviyo API Secret Detector (#1870)
* Detector-Competition-Feat: Add Klaviyo API Secret Detector

* fix(error): add s1.VerificationError and remove specific code check.

* fix(error): add s1.VerificationError and remove specific code check.
2023-10-11 08:35:04 -06:00
Dustin Decker 52ed87edb7 Add an option to filter unverified results using shannon entropy (#1875)
* Add an option to filter unverified results using shannon entropy

* lint

* add test, update test, and optimize
2023-10-08 19:52:28 -07:00
Dustin Decker b960231155 Add TravisCI source 2023-10-08 19:44:13 -07:00
Miccah f09bce3f75 [chore] Fix flaky TestJobProgressElapsedTime (#1872) 2023-10-06 17:05:05 -07:00
Dustin Decker 22ee2c5b07 Tighten up keywords (#1874) 2023-10-06 16:28:51 -07:00
Corben Leo 77a82847af Detector-Competition-Fix: fix notion.so false negative verification (#1866)
* Detector-Competition-Fix: fix notion.so false negative verification

* Detector-Competition-Fix: fix notion.so verification
2023-10-05 12:27:06 -05:00
Corben Leo 179a7e4cbc Detector-Competition-New: add anthropic api key detector (#1861)
* feat(anthropic): add anthropic api key detector

* Detector-Competition-Fix: fix remove debug println
2023-10-05 11:34:40 -05:00
Corben Leo bf1cce43e5 Detector-Competition-New: add ramp.com client id & secret detector (#1862) 2023-10-05 09:40:30 -05:00
ahrav 3d2490ca80 use Repositories field from conn. (#1860) 2023-10-04 13:56:02 -07:00
Miccah 273f1077af Add include and ignore list to Artifactory (#1857)
* Add include and ignore list to Artifactory proto

* Generate protos
2023-10-03 16:48:30 -07:00
ahrav cee456f484 support insecure TLS for Jira and Jenkins (#1856)
* support insecure TLS for Jira and Jenkins

* lint
2023-10-03 09:55:38 -07:00
āh̳̕mͭͭͨͩ̐e̘ͬ́͋ͬ̊̓͂d 8d2d8c8395 add tristate verification to postman (#1837) 2023-10-03 12:38:43 -04:00
Hon Kwok 4598244167 Use placeholder as default if field left empty and is required (#1642)
* Use placeholder as default if field left empty and is required

Co-authored-by: mcastorina <m.castorina93@gmail.com>

* Drop unused func

* uncomment azure source

* update wording

---------

Co-authored-by: mcastorina <m.castorina93@gmail.com>
2023-10-02 17:21:40 -07:00
Dylan Ayrey de535071e1 implemented planet scale creds (passwords and API keys) (#1841)
* implemented planet scale creds (passwords and API keys)

* Add timeout, fix tests, fix indeterminate

---------

Co-authored-by: counter <counter@counters-MacBook-Air.local>
Co-authored-by: Dustin Decker <dustin@trufflesec.com>
2023-10-02 14:00:36 -07:00
Dylan Ayrey f13fe36ae2 adding azure storage detector (#1840)
* adding azure storage detector

* Fix variable name

* Escape regex

* fix test fields and update expected status code

---------

Co-authored-by: counter <counter@counters-MacBook-Air.local>
Co-authored-by: Dustin Decker <dustin@trufflesec.com>
2023-10-02 13:45:45 -07:00
Dylan Ayrey b3555f5419 Adding Howtorotate Guides to TruffleHog (#1839)
* adding how to rotate guides

* Adding project ID to metadata

* update key name, remove comments, and ensure always present

---------

Co-authored-by: counter <counter@counters-MacBook-Air.local>
Co-authored-by: Dustin Decker <dustin@trufflesec.com>
2023-10-02 13:45:17 -07:00
āh̳̕mͭͭͨͩ̐e̘ͬ́͋ͬ̊̓͂d f8f0c984fb update pagerdutyapikey detector to tri-state verification (#1836) 2023-10-02 16:33:18 -04:00
Miccah 0d451aa806 Fix bug in chunker that surfaces with a flaky passed in io.Reader (#1838)
* Fix bug in chunker that surfaces with a flaky passed in io.Reader

The chunker was previously expecting the passed in io.Reader to always
successfully read a full buffer of data, however it's valid for a Reader
to return less data than requested. When this happens, the chunker would
peek the same data that it then reads in the next iteration of the loop,
causing the same data to be scanned twice.

Co-authored-by: ahrav <ahravdutta02@gmail.com>

* Fix EOF error check

* Use io.ReadFull in Chunker

---------

Co-authored-by: ahrav <ahravdutta02@gmail.com>
2023-10-02 09:38:23 -07:00
dependabot[bot] a750b8ef2b Bump github.com/AzureAD/microsoft-authentication-library-for-go (#1850)
Bumps [github.com/AzureAD/microsoft-authentication-library-for-go](https://github.com/AzureAD/microsoft-authentication-library-for-go) from 0.5.1 to 1.2.0.
- [Release notes](https://github.com/AzureAD/microsoft-authentication-library-for-go/releases)
- [Changelog](https://github.com/AzureAD/microsoft-authentication-library-for-go/blob/main/RELEASES.md)
- [Commits](https://github.com/AzureAD/microsoft-authentication-library-for-go/compare/v0.5.1...v1.2.0)

---
updated-dependencies:
- dependency-name: github.com/AzureAD/microsoft-authentication-library-for-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 09:31:58 -07:00
dependabot[bot] 67fc50a4fb Bump github.com/bradleyfalzon/ghinstallation/v2 from 2.6.0 to 2.7.0 (#1851)
Bumps [github.com/bradleyfalzon/ghinstallation/v2](https://github.com/bradleyfalzon/ghinstallation) from 2.6.0 to 2.7.0.
- [Release notes](https://github.com/bradleyfalzon/ghinstallation/releases)
- [Commits](https://github.com/bradleyfalzon/ghinstallation/compare/v2.6.0...v2.7.0)

---
updated-dependencies:
- dependency-name: github.com/bradleyfalzon/ghinstallation/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 08:55:03 -07:00
87 changed files with 4710 additions and 1311 deletions
+9 -8
View File
@@ -11,7 +11,7 @@ require (
cloud.google.com/go/secretmanager v1.11.1
cloud.google.com/go/storage v1.33.0
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11
github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0
github.com/BobuSumisu/aho-corasick v1.0.3
github.com/TheZeroSlave/zapsentry v1.17.0
github.com/aws/aws-sdk-go v1.45.19
@@ -19,7 +19,7 @@ require (
github.com/bill-rich/disk-buffer-reader v0.1.7
github.com/bill-rich/go-syslog v0.0.0-20220413021637-49edb52a574c
github.com/bitfinexcom/bitfinex-api-go v0.0.0-20210608095005-9e0b26f200fb
github.com/bradleyfalzon/ghinstallation/v2 v2.6.0
github.com/bradleyfalzon/ghinstallation/v2 v2.7.0
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/glamour v0.6.0
@@ -65,6 +65,7 @@ require (
github.com/prometheus/client_golang v1.16.0
github.com/rabbitmq/amqp091-go v1.8.1
github.com/sergi/go-diff v1.3.1
github.com/shuheiktgw/go-travis v0.3.1
github.com/snowflakedb/gosnowflake v1.6.23
github.com/stretchr/testify v1.8.4
github.com/tailscale/depaware v0.0.0-20210622194025-720c4b409502
@@ -72,7 +73,7 @@ require (
go.mongodb.org/mongo-driver v1.12.1
go.uber.org/mock v0.2.0
go.uber.org/zap v1.24.0
golang.org/x/crypto v0.13.0
golang.org/x/crypto v0.14.0
golang.org/x/exp v0.0.0-20230206171751-46f607a40771
golang.org/x/oauth2 v0.12.0
golang.org/x/sync v0.3.0
@@ -166,7 +167,7 @@ require (
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v23.1.21+incompatible // indirect
github.com/google/go-github/v53 v53.2.0 // indirect
github.com/google/go-github/v55 v55.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/google/s2a-go v0.1.4 // indirect
@@ -205,7 +206,7 @@ require (
github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8 // indirect
github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/montanaflynn/stats v0.6.6 // indirect
github.com/montanaflynn/stats v0.7.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
@@ -246,9 +247,9 @@ require (
go.uber.org/multierr v1.6.0 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/term v0.12.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
+25 -19
View File
@@ -13,7 +13,6 @@ cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbf
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
@@ -65,8 +64,8 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1 h1:BWe8a+f/t+7KY7zH2mqygeUD0t8hNFXe08p1Pb3/jKE=
github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BobuSumisu/aho-corasick v1.0.3 h1:uuf+JHwU9CHP2Vx+wAy6jcksJThhJS9ehR8a+4nPE9g=
github.com/BobuSumisu/aho-corasick v1.0.3/go.mod h1:hm4jLcvZKI2vRF2WDU1N4p/jpWtpOzp3nLmi9AzX/XE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -168,8 +167,8 @@ github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bradleyfalzon/ghinstallation/v2 v2.6.0 h1:IRY7Xy588KylkoycsUhFpW7cdGpy5Y5BPsz4IfuJtGk=
github.com/bradleyfalzon/ghinstallation/v2 v2.6.0/go.mod h1:oQ3etOwN3TRH4EwgW5/7MxSVMGlMlzG/O8TU7eYdoSk=
github.com/bradleyfalzon/ghinstallation/v2 v2.7.0 h1:ranXaC3Zz/F6G/f0Joj3LrFp2OzOKfJZev5Q7OaMc88=
github.com/bradleyfalzon/ghinstallation/v2 v2.7.0/go.mod h1:ymxfmloxXBFXvvF1KpeUhOQM6Dfz9NYtfvTiJyk82UE=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -304,13 +303,14 @@ github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -367,8 +367,9 @@ github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
github.com/google/go-github/v42 v42.0.0 h1:YNT0FwjPrEysRkLIiKuEfSvBPCGKphW5aS5PxwaoLec=
github.com/google/go-github/v42 v42.0.0/go.mod h1:jgg/jvyI0YlDOM1/ps6XYh04HNQ3vKf0CVko62/EhRg=
github.com/google/go-github/v53 v53.2.0 h1:wvz3FyF53v4BK+AsnvCmeNhf8AkTaeh2SoYu/XUvTtI=
github.com/google/go-github/v53 v53.2.0/go.mod h1:XhFRObz+m/l+UCm9b7KSIC3lT3NWSXGt7mOsAWEloao=
github.com/google/go-github/v55 v55.0.0 h1:4pp/1tNMB9X/LuAhs5i0KQAE40NmiR/y6prLNb9x9cg=
github.com/google/go-github/v55 v55.0.0/go.mod h1:JLahOTA1DnXzhxEymmFF5PP2tSS9JVNj68mSZNDwskA=
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
@@ -383,7 +384,6 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8I
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
@@ -524,8 +524,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ=
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/muesli/ansi v0.0.0-20211031195517-c9f0611b6c70 h1:kMlmsLSbjkikxQJ1IPwaM+7LJ9ltFu/fi8CRzvSnQmA=
@@ -571,7 +571,6 @@ github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTw
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7 h1:+/+DxvQaYifJ+grD4klzrS5y+KJXldn/2YTl5JG+vZ8=
@@ -607,6 +606,8 @@ github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shuheiktgw/go-travis v0.3.1 h1:SAT16mi77ccqogOslnXxBXzXbpeyChaIYUwi2aJpVZY=
github.com/shuheiktgw/go-travis v0.3.1/go.mod h1:avnFFDqJDdRHwlF9tgqvYi3asQCm/HGL8aLxYiKa4Yg=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@@ -708,8 +709,10 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -774,14 +777,13 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
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.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -841,8 +843,10 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -850,8 +854,10 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -864,6 +870,7 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
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.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -972,7 +979,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
+9
View File
@@ -48,6 +48,7 @@ var (
noVerification = cli.Flag("no-verification", "Don't verify the results.").Bool()
onlyVerified = cli.Flag("only-verified", "Only output verified results.").Bool()
filterUnverified = cli.Flag("filter-unverified", "Only output first unverified result per chunk per detector if there are more than one results.").Bool()
filterEntropy = cli.Flag("filter-entropy", "Filter unverified results with Shannon entropy. Start with 3.0.").Float64()
configFilename = cli.Flag("config", "Path to configuration file.").ExistingFile()
// rules = cli.Flag("rules", "Path to file with custom rules.").String()
printAvgDetectorTime = cli.Flag("print-avg-detector-time", "Print the average time spent on each detector.").Bool()
@@ -138,6 +139,9 @@ var (
dockerScan = cli.Command("docker", "Scan Docker Image")
dockerScanImages = dockerScan.Flag("image", "Docker image to scan. Use the file:// prefix to point to a local tarball, otherwise a image registry is assumed.").Required().Strings()
travisCiScan = cli.Command("travisci", "Scan TravisCI")
travisCiScanToken = travisCiScan.Flag("token", "TravisCI token. Can also be provided with environment variable").Envar("TRAVISCI_TOKEN").Required().String()
)
func init() {
@@ -370,6 +374,7 @@ func run(state overseer.State) {
engine.WithOnlyVerified(*onlyVerified),
engine.WithPrintAvgDetectorTime(*printAvgDetectorTime),
engine.WithPrinter(printer),
engine.WithFilterEntropy(*filterEntropy),
)
if err != nil {
logFatal(err, "error initializing engine")
@@ -496,6 +501,10 @@ func run(state overseer.State) {
if err := e.ScanCircleCI(ctx, *circleCiScanToken); err != nil {
logFatal(err, "Failed to scan CircleCI.")
}
case travisCiScan.FullCommand():
if err := e.ScanTravisCI(ctx, *travisCiScanToken); err != nil {
logFatal(err, "Failed to scan TravisCI.")
}
case gcsScan.FullCommand():
cfg := sources.GCSConfig{
ProjectID: *gcsProjectID,
@@ -55,6 +55,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
Raw: []byte(resMatch),
RawV2: []byte(resMatch + resIdMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/airbrake/",
}
if verify {
payload := strings.NewReader(`{"environment":"production","username":"john","email":"john@smith.com","repository":"https://github.com/airbrake/airbrake","revision":"38748467ea579e7ae64f7815452307c9d05e05c5","version":"v2.0"}`)
@@ -45,6 +45,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_AirbrakeUserKey,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/airbrake/",
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.airbrake.io/api/v4/projects?key="+resMatch, nil)
+27 -2
View File
@@ -2,6 +2,7 @@ package airtableapikey
import (
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
@@ -20,8 +21,9 @@ var _ detectors.Detector = (*Scanner)(nil)
var (
client = common.SaneHttpClient()
appPat = regexp.MustCompile(`(app[a-zA-Z0-9_-]{14})`) // could be part of url
keyPat = regexp.MustCompile(`\b(key[a-zA-Z0-9_-]{14})\b`)
appPat = regexp.MustCompile(`(app[a-zA-Z0-9_-]{14})`) // could be part of url
keyPat = regexp.MustCompile(`\b(key[a-zA-Z0-9_-]{14})\b`)
personalPat = regexp.MustCompile(`(\bpat[[:alnum:]]{14}\.[[:alnum:]]{64}\b)`)
)
// Keywords are used for efficiently pre-filtering chunks.
@@ -30,17 +32,31 @@ func (s Scanner) Keywords() []string {
return []string{"airtable"}
}
type response struct {
Error struct {
Type string `json:"type"`
Message string `json:"message"`
} `json:"error"`
}
// FromData will find and optionally verify AirtableApiKey secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
appMatches := appPat.FindAllStringSubmatch(dataStr, -1)
keyMatches := keyPat.FindAllStringSubmatch(dataStr, -1)
personalKeyMatches := personalPat.FindAllStringSubmatch(dataStr, -1)
if len(keyMatches) == 0 {
keyMatches = personalKeyMatches
}
for _, keyMatch := range keyMatches {
if len(keyMatch) != 2 {
continue
}
keyRes := strings.TrimSpace(keyMatch[1])
for _, appMatch := range appMatches {
@@ -67,6 +83,15 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 403 {
var resp response
if err = json.NewDecoder(res.Body).Decode(&resp); err == nil {
// check if the error is due to invalid permissions or model not found
if resp.Error.Type == "INVALID_PERMISSIONS_OR_MODEL_NOT_FOUND" {
// The key is verified as it works, but the user must enumerate the tables or permissions for the key.
s1.Verified = true
}
}
} else {
if detectors.IsKnownFalsePositive(keyRes, detectors.DefaultFalsePositives, true) {
continue
@@ -9,9 +9,10 @@ import (
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"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/pb/detectorspb"
)
@@ -99,8 +100,9 @@ func TestAirtableApiKey_FromChunk(t *testing.T) {
}
got[i].Raw = nil
}
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("AirtableApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Airtable.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
+118
View File
@@ -0,0 +1,118 @@
package anthropic
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(`\b(sk-ant-api03-[\w\-]{93}AA)\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"sk-ant-api03"}
}
type response struct {
Error struct {
Type string `json:"type"`
Message string `json:"message"`
} `json:"error"`
}
// FromData will find and optionally verify Anthropic secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 2 {
continue
}
resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Anthropic,
Raw: []byte(resMatch),
}
if verify {
client := s.client
if client == nil {
client = defaultClient
}
body := map[string]interface{}{
"prompt": "test",
}
bodyBytes, err := json.Marshal(body)
if err != nil {
continue
}
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.anthropic.com/v1/complete", bytes.NewReader(bodyBytes))
if err != nil {
continue
}
req.Header.Set("x-api-key", resMatch)
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode == http.StatusBadRequest {
var resp response
if err = json.NewDecoder(res.Body).Decode(&resp); err != nil {
s1.VerificationError = fmt.Errorf("unexpected HTTP response body: %w", err)
continue
}
if resp.Error.Message == "max_tokens_to_sample: field required" {
// The secret is verified
// Anthropic returns 400 on a request containing a valid API key,
// when not containing a valid model field
s1.Verified = true
}
} else if res.StatusCode == 401 {
// The secret is determinately not verified (nothing to do)
// Anthropic returns 401 on all requests not containing a valid x-api-key header
} else {
s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
} else {
s1.VerificationError = err
}
}
// This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key.
if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, s1)
}
return results, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Anthropic
}
+161
View File
@@ -0,0 +1,161 @@
//go:build detectors
// +build detectors
package anthropic
import (
"context"
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestAnthropic_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("ANTHROPIC")
inactiveSecret := testSecrets.MustGetField("ANTHROPIC_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Anthropic,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a anthropic secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Anthropic,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, would be verified if not for timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Anthropic,
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 anthropic secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Anthropic,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Anthropic.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Anthropic.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+7 -5
View File
@@ -206,6 +206,10 @@ func (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch str
client = defaultVerificationClient
}
extraData := map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/aws/",
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
@@ -213,11 +217,9 @@ func (s scanner) verifyMatch(ctx context.Context, resIDMatch, resSecretMatch str
identityInfo := identityRes{}
err := json.NewDecoder(res.Body).Decode(&identityInfo)
if err == nil {
extraData := map[string]string{
"account": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Account,
"user_id": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.UserID,
"arn": identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Arn,
}
extraData["account"] = identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Account
extraData["user_id"] = identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.UserID
extraData["arn"] = identityInfo.GetCallerIdentityResponse.GetCallerIdentityResult.Arn
return true, extraData, nil
} else {
return false, nil, err
+4
View File
@@ -57,6 +57,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
RawV2: []byte(clientID[2] + clientSecret[2] + tenantID[2]),
Redacted: clientID[2],
}
// Set the RotationGuideURL in the ExtraData
s.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/azure/",
}
if verify {
cred := auth.NewClientCredentialsConfig(clientID[2], clientSecret[2], tenantID[2])
@@ -0,0 +1,94 @@
package azurestorage
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"regexp"
"strings"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
}
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = http.DefaultClient
keyPat = regexp.MustCompile(`DefaultEndpointsProtocol=https;AccountName=(?P<account_name>[^;]+);AccountKey=(?P<account_key>[^;]+);EndpointSuffix=core\.windows\.net`)
)
func (s Scanner) Keywords() []string {
return []string{"DefaultEndpointsProtocol=https;AccountName="}
}
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 3 {
continue
}
accountName := strings.TrimSpace(match[1])
accountKey := strings.TrimSpace(match[2])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_AzureStorage,
Raw: []byte(accountName),
}
if verify {
client := s.client
if client == nil {
client = defaultClient
}
now := time.Now().UTC().Format(http.TimeFormat)
stringToSign := "GET\n\n\n\n\n\n\n\n\n\n\n\nx-ms-date:" + now + "\nx-ms-version:2019-12-12\n/" + accountName + "/\ncomp:list"
accountKeyBytes, _ := base64.StdEncoding.DecodeString(accountKey)
h := hmac.New(sha256.New, accountKeyBytes)
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
url := "https://" + accountName + ".blob.core.windows.net/?comp=list"
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
req.Header.Set("x-ms-date", now)
req.Header.Set("x-ms-version", "2019-12-12")
req.Header.Set("Authorization", "SharedKey "+accountName+":"+signature)
if err != nil {
continue
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 403 {
} else {
s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
} else {
s1.VerificationError = err
}
}
results = append(results, s1)
}
return results, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_AzureStorage
}
@@ -0,0 +1,162 @@
//go:build detectors
// +build detectors
package azurestorage
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestAzurestorage_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("AZURE_STORAGE")
inactiveSecret := testSecrets.MustGetField("AZURE_STORAGE_INACTIVE")
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 azurestorage secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AzureStorage,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a azurestorage secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AzureStorage,
Verified: false,
},
},
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 azurestorage secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AzureStorage,
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 azurestorage secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_AzureStorage,
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("Azuretorage.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Azurestorage.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+3 -3
View File
@@ -12,15 +12,15 @@ func TestPrefixRegex(t *testing.T) {
}{
{
keywords: []string{"securitytrails"},
expected: `(?i)(?:securitytrails).|(?:[\n\r]){0,40}`,
expected: `(?i)(?:securitytrails)(?:.|[\n\r]){0,40}`,
},
{
keywords: []string{"zipbooks"},
expected: `(?i)(?:zipbooks).|(?:[\n\r]){0,40}`,
expected: `(?i)(?:zipbooks)(?:.|[\n\r]){0,40}`,
},
{
keywords: []string{"wrike"},
expected: `(?i)(?:wrike).|(?:[\n\r]){0,40}`,
expected: `(?i)(?:wrike)(?:.|[\n\r]){0,40}`,
},
}
for _, tt := range tests {
+37
View File
@@ -2,6 +2,7 @@ package detectors
import (
_ "embed"
"math"
"strings"
"unicode"
)
@@ -90,3 +91,39 @@ func bytesToCleanWordList(data []byte) []string {
}
return words
}
func StringShannonEntropy(input string) float64 {
chars := make(map[rune]float64)
inverseTotal := 1 / float64(len(input)) // precompute the inverse
for _, char := range input {
chars[char]++
}
entropy := 0.0
for _, count := range chars {
probability := count * inverseTotal
entropy += probability * math.Log2(probability)
}
return -entropy
}
// FilterResultsWithEntropy filters out determinately unverified results that have a shannon entropy below the given value.
func FilterResultsWithEntropy(results []Result, entropy float64) []Result {
filteredResults := []Result{}
for _, result := range results {
if !result.Verified && result.VerificationError == nil {
if result.RawV2 != nil {
if StringShannonEntropy(string(result.RawV2)) >= entropy {
filteredResults = append(filteredResults, result)
}
} else {
if StringShannonEntropy(string(result.Raw)) >= entropy {
filteredResults = append(filteredResults, result)
}
}
}
}
return filteredResults
}
+44 -1
View File
@@ -3,7 +3,10 @@
package detectors
import "testing"
import (
_ "embed"
"testing"
)
func TestIsFalsePositive(t *testing.T) {
type args struct {
@@ -40,3 +43,43 @@ func TestIsFalsePositive(t *testing.T) {
})
}
}
func TestStringShannonEntropy(t *testing.T) {
type args struct {
input string
}
tests := []struct {
name string
args args
want float64
}{
{
name: "entropy 1",
args: args{
input: "aaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
want: 0,
},
{
name: "entropy 2",
args: args{
input: "aaaaaaaaaaaaaaaaaaaaaaaaaaab",
},
want: 0.22228483068568816,
},
{
name: "entropy 3",
args: args{
input: "aaaaaaaaaaaaaaaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaab",
},
want: 0.22228483068568816,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := StringShannonEntropy(tt.args.input); got != tt.want {
t.Errorf("StringShannonEntropy() = %v, want %v", got, tt.want)
}
})
}
}
+5
View File
@@ -87,6 +87,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
RawV2: credBytes,
Redacted: creds.ClientEmail,
}
// Set the RotationGuideURL in the ExtraData
s.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/gcp/",
"project": creds.ProjectID,
}
if verify {
credentials, err := google.CredentialsFromJSON(ctx, credBytes, "https://www.googleapis.com/auth/cloud-platform")
+9 -8
View File
@@ -68,6 +68,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Github,
Raw: []byte(token),
ExtraData: map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
},
}
if verify {
@@ -88,14 +91,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
res.Body.Close()
if err == nil {
s1.Verified = true
s1.ExtraData = map[string]string{
"username": userResponse.Login,
"url": userResponse.UserURL,
"account_type": userResponse.Type,
"site_admin": fmt.Sprintf("%t", userResponse.SiteAdmin),
"name": userResponse.Name,
"company": userResponse.Company,
}
s1.ExtraData["username"] = userResponse.Login
s1.ExtraData["url"] = userResponse.UserURL
s1.ExtraData["account_type"] = userResponse.Type
s1.ExtraData["site_admin"] = fmt.Sprintf("%t", userResponse.SiteAdmin)
s1.ExtraData["name"] = userResponse.Name
s1.ExtraData["company"] = userResponse.Company
}
}
}
@@ -54,6 +54,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
Raw: []byte(idMatch[1]),
RawV2: []byte(idMatch[1] + secretMatch[1]),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
}
config := &clientcredentials.Config{
ClientID: idMatch[1],
+3
View File
@@ -69,6 +69,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Github,
Raw: []byte(token),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
}
if verify {
client := common.SaneHttpClient()
+3
View File
@@ -57,6 +57,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_GitHubApp,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/github/",
}
if verify {
signKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(resMatch))
+3
View File
@@ -55,6 +55,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Gitlab,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/gitlab/",
}
if verify {
isVerified, verificationErr := s.verifyGitlab(ctx, resMatch)
+3
View File
@@ -51,6 +51,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Gitlab,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/gitlab/",
}
if verify {
isVerified, verificationErr := s.verifyGitlab(ctx, resMatch)
+3
View File
@@ -73,6 +73,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
Raw: []byte(resToken),
RawV2: []byte(fmt.Sprintf("%s:%s:%s", resEmail, resToken, resDomain)),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/atlassian/",
}
if verify {
client := s.getClient()
+119
View File
@@ -0,0 +1,119 @@
package klaviyo
import (
"context"
"encoding/json"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(`\b(pk_[[:alnum:]]{34})\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"pk_"}
}
type response struct {
Errors []struct {
Id string `json:"id"`
Status int `json:"status"`
Code string `json:"code"`
Title string `json:"title"`
Detail string `json:"detail"`
Source struct {
Pointer string `json:"pointer"`
} `json:"source"`
} `json:"errors"`
}
// FromData will find and optionally verify Klaviyo secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 2 {
continue
}
resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Klaviyo,
Raw: []byte(resMatch),
}
if verify {
client := s.client
if client == nil {
client = defaultClient
}
req, err := http.NewRequestWithContext(ctx, "GET", "https://a.klaviyo.com/api/profiles", nil)
if err != nil {
continue
}
req.Header.Set("Revision", "2023-02-22")
req.Header.Set("Authorization", "Klaviyo-API-Key "+resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 403 {
var apiResp response
// Klaviyo responds with 403 when the API-key does not have permissions to hit /api/profiles.
// Ensure that the 403 is from Klaviyo: https://developers.klaviyo.com/en/docs/rate_limits_and_error_handling
if err = json.NewDecoder(res.Body).Decode(&apiResp); err == nil {
// valid JSON response
if len(apiResp.Errors) > 0 {
// Thus, the key is verified, but it is up to the user to determine what scopes the key has.
s1.Verified = true
} else {
s1.VerificationError = fmt.Errorf("errors expected")
}
} else {
s1.VerificationError = fmt.Errorf("unexpected API JSON response")
}
} else if res.StatusCode == 401 {
// The secret is determinately not verified (nothing to do)
} else {
s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
} else {
s1.VerificationError = err
}
}
// This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key.
if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, s1)
}
return results, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Klaviyo
}
+161
View File
@@ -0,0 +1,161 @@
//go:build detectors
// +build detectors
package klaviyo
import (
"context"
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestKlaviyo_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("KLAVIYO")
inactiveSecret := testSecrets.MustGetField("KLAVIYO_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a klaviyo secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Klaviyo,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a klaviyo secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Klaviyo,
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 klaviyo secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Klaviyo,
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 klaviyo secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Klaviyo,
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("Klaviyo.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Klaviyo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+3
View File
@@ -42,6 +42,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Mailchimp,
Raw: []byte(match),
}
s.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/mailchimp/",
}
if verify {
datacenter := strings.Split(match, "-")[1]
@@ -50,6 +50,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_MicrosoftTeamsWebhook,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/microsoftteams/",
}
if verify {
client := s.client
+5 -2
View File
@@ -2,8 +2,6 @@ package mongodb
import (
"context"
"go.mongodb.org/mongo-driver/x/mongo/driver/auth"
"go.mongodb.org/mongo-driver/x/mongo/driver/topology"
"regexp"
"strings"
"time"
@@ -11,6 +9,8 @@ import (
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref"
"go.mongodb.org/mongo-driver/x/mongo/driver/auth"
"go.mongodb.org/mongo-driver/x/mongo/driver/topology"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -49,6 +49,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_MongoDB,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/mongo/",
}
if verify {
timeout := s.timeout
+3
View File
@@ -45,6 +45,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Netlify,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/netlify/",
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.netlify.com/api/v1/sites", nil)
+12 -2
View File
@@ -51,19 +51,29 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
if err != nil {
continue
}
req.Header.Add("Notion-Version", "2021-05-11")
req.Header.Add("Notion-Version", "2022-06-28")
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
if res.StatusCode >= 200 && res.StatusCode < 300 || res.StatusCode == 403 {
// if >= 200 and < 300, the secret is valid and has privileges for the /v1/users endpoint
// If 403, the secret is valid, but does not have privileges for the /v1/users endpoint,
// Notion returns 401 for all non-valid keys, thus 403 indicates it has fine-tuned permissions,
// /v1/search, /v1/databases/*, etc. may work.
s1.Verified = true
} else if res.StatusCode == 401 {
// The secret is determinately not verified (nothing to do)
// Nothing returns 401 on all requests not containing a api key.
} else {
// This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key.
if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
}
} else {
s1.VerificationError = err
}
}
+3
View File
@@ -47,6 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_NpmToken,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/npm/",
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://registry.npmjs.org/-/whoami", nil)
+3
View File
@@ -48,6 +48,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_NpmToken,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/npm/",
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://registry.npmjs.org/-/whoami", nil)
@@ -12,28 +12,30 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct{}
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
client = common.SaneHttpClient()
const verifyURL = "https://api.pagerduty.com/users"
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"pagerduty", "pd"}) + `\b([a-zA-Z0-9_+-]{20})\b`)
keyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"pagerduty", "pager_duty", "pd_", "pd-"}) + `\b([a-zA-Z0-9_+-]{20})\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"pagerduty", "pd"}
return []string{"pagerduty", "pager_duty", "pd_", "pd-"}
}
// FromData will find and optionally verify PagerDutyApiKey secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
@@ -48,34 +50,52 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.pagerduty.com/users", nil)
if err != nil {
continue
}
req.Header.Add("Accept", "application/vnd.pagerduty+json;version=2")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Token %s", resMatch))
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
}
}
client := s.getClient()
isVerified, verificationErr := verifyPagerdutyapikey(ctx, client, resMatch)
s1.Verified = isVerified
s1.VerificationError = verificationErr
}
if !s1.Verified {
if detectors.IsKnownFalsePositive(string(s1.Raw), detectors.DefaultFalsePositives, true) {
continue
}
if !s1.Verified && detectors.IsKnownFalsePositive(string(s1.Raw), detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, s1)
}
return results, nil
}
func (s Scanner) getClient() *http.Client {
if s.client != nil {
return s.client
}
return defaultClient
}
func verifyPagerdutyapikey(ctx context.Context, client *http.Client, token string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, verifyURL, nil)
if err != nil {
return false, err
}
req.Header.Add("Accept", "application/vnd.pagerduty+json;version=2")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Token %s", token))
res, err := client.Do(req)
if err != nil {
return false, err
}
defer res.Body.Close()
switch res.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_PagerDutyApiKey
}
@@ -9,8 +9,8 @@ import (
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
@@ -32,11 +32,12 @@ func TestPagerDutyApiKey_FromChunk(t *testing.T) {
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
@@ -54,6 +55,40 @@ func TestPagerDutyApiKey_FromChunk(t *testing.T) {
},
wantErr: false,
},
{
name: "found, would be verified if not for timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a pagerdutyapikey secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PagerDutyApiKey,
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 pagerdutyapikey secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PagerDutyApiKey,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
{
name: "found, unverified",
s: Scanner{},
@@ -84,8 +119,7 @@ func TestPagerDutyApiKey_FromChunk(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Scanner{}
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("PagerDutyApiKey.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -94,9 +128,12 @@ func TestPagerDutyApiKey_FromChunk(t *testing.T) {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
got[i].Raw = nil
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Errorf("PagerDutyApiKey.FromData() verificationError = %v, wantVerificationErr %v", got[i].VerificationError, tt.wantVerificationErr)
}
}
if diff := pretty.Compare(got, tt.want); diff != "" {
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("PagerDutyApiKey.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
+88
View File
@@ -0,0 +1,88 @@
package planetscale
import (
"context"
"fmt"
"net/http"
"regexp"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
}
var (
defaultClient = common.SaneHttpClient()
usernamePat = regexp.MustCompile(`\b[a-z0-9]{12}\b`)
passwordPat = regexp.MustCompile(`\bpscale_tkn_[A-Za-z0-9_]{43}\b`)
)
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
// Keywords are used for efficiently pre-filtering chunks.
func (s Scanner) Keywords() []string {
return []string{"pscale_tkn_"}
}
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
usernameMatches := usernamePat.FindAllString(dataStr, -1)
passwordMatches := passwordPat.FindAllString(dataStr, -1)
for _, username := range usernameMatches {
for _, password := range passwordMatches {
credentials := fmt.Sprintf("%s:%s", username, password)
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_PlanetScale,
Raw: []byte(credentials),
}
if verify {
client := s.client
if client == nil {
client = defaultClient
}
// Construct HTTP request
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.planetscale.com/v1/organizations", nil)
if err != nil {
continue
}
req.Header.Set("Authorization", credentials)
req.Header.Set("accept", "application/json")
// Send HTTP request
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 401 {
// The secret is determinately not verified
s1.Verified = false
} else {
s1.VerificationError = fmt.Errorf("unexpected status code %d", res.StatusCode)
}
} else {
s1.VerificationError = err
}
}
results = append(results, s1)
}
}
return results, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_PlanetScale
}
@@ -0,0 +1,164 @@
//go:build detectors
// +build detectors
package planetscale
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestPlanetscale_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("PLANET_SCALE_TOKEN")
secretID := testSecrets.MustGetField("PLANET_SCALE_ID")
inactiveSecret := testSecrets.MustGetField("PLANET_SCALE_TOKEN_INACTIVE")
inactiveSecretID := testSecrets.MustGetField("PLANET_SCALE_ID_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a planetscale secret %s within with id %s", secret, secretID)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PlanetScale,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a planetscale secret %s within with id %s but not valid", inactiveSecret, inactiveSecretID)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PlanetScale,
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 planetscale secret %s within with id %s", secret, secretID)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PlanetScale,
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 planetscale secret %s within with id %s", secret, secretID)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PlanetScale,
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("Planetscale.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Planetscale.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
@@ -0,0 +1,80 @@
package planetscaledb
import (
"context"
"database/sql"
"regexp"
"strings"
"github.com/go-sql-driver/mysql"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct{}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
usernamePat = regexp.MustCompile(`\b[a-z0-9]{20}\b`)
passwordPat = regexp.MustCompile(`\bpscale_pw_[A-Za-z0-9_]{43}\b`)
hostPat = regexp.MustCompile(`\b(aws|gcp)\.connect\.psdb\.cloud\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
func (s Scanner) Keywords() []string {
return []string{"pscale_pw_"}
}
// FromData will find and optionally verify Planetscaledb secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
usernameMatches := usernamePat.FindAllStringSubmatch(dataStr, -1)
passwordMatches := passwordPat.FindAllStringSubmatch(dataStr, -1)
hostMatches := hostPat.FindAllString(dataStr, -1)
for _, username := range usernameMatches {
for _, password := range passwordMatches {
for _, host := range hostMatches {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_PlanetScaleDb,
Raw: []byte(strings.Join([]string{host, username[0], password[0]}, "\t")),
}
if verify {
cfg := mysql.Config{
User: username[0],
Passwd: password[0],
Net: "tcp",
Addr: host,
TLSConfig: "true", // assuming SSL is required
AllowNativePasswords: true,
}
db, err := sql.Open("mysql", cfg.FormatDSN())
if err != nil {
s1.VerificationError = err
} else {
err = db.PingContext(ctx)
if err == nil {
s1.Verified = true
} else {
s1.VerificationError = err
}
db.Close()
}
}
results = append(results, s1)
}
}
}
return results, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_PlanetScaleDb
}
@@ -0,0 +1,129 @@
//go:build detectors
// +build detectors
package planetscaledb
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestPlanetscaledb_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)
}
username := testSecrets.MustGetField("PLANET_SCALEDB_USERNAME")
host := testSecrets.MustGetField("PLANET_SCALEDB_HOST")
password := testSecrets.MustGetField("PLANET_SCALEDB_PASSWORD")
inactivePassword := testSecrets.MustGetField("PLANET_SCALEDB_PASSWORD_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a planetscaledb secret %s %s %s", username, password, host)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PlanetScaleDb,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a planetscaledb secret %s %s %s", username, inactivePassword, host)),
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_PlanetScaleDb,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
wantVerificationErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Planetscaledb.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Planetscaledb.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+43 -19
View File
@@ -2,6 +2,7 @@ package postman
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
@@ -11,13 +12,17 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct{}
type Scanner struct {
client *http.Client
}
const verifyURL = "https://api.getpostman.com/collections"
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
client = common.SaneHttpClient()
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(`\b(PMAK-[a-zA-Z-0-9]{59})\b`)
@@ -47,31 +52,50 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.getpostman.com/collections", nil)
if err != nil {
continue
}
req.Header.Add("x-api-key", resMatch)
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else {
// This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key.
if detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
}
}
client := s.getClient()
isVerified, verificationErr := verifyPostman(ctx, client, resMatch)
s1.Verified = isVerified
s1.VerificationError = verificationErr
}
if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, s1)
}
return results, nil
}
func (s Scanner) getClient() *http.Client {
if s.client != nil {
return s.client
}
return defaultClient
}
func verifyPostman(ctx context.Context, client *http.Client, token string) (bool, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, verifyURL, nil)
if err != nil {
return false, err
}
req.Header.Add("x-api-key", token)
res, err := client.Do(req)
if err != nil {
return false, err
}
defer res.Body.Close()
switch res.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized:
return false, nil
default:
return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Postman
}
+48 -10
View File
@@ -9,7 +9,8 @@ import (
"testing"
"time"
"github.com/kylelemons/godebug/pretty"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
@@ -32,11 +33,12 @@ func TestPostman_FromChunk(t *testing.T) {
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
@@ -54,6 +56,40 @@ func TestPostman_FromChunk(t *testing.T) {
},
wantErr: false,
},
{
name: "found, would be verified if not for timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a postman secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postman,
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 postman secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Postman,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
{
name: "found, unverified",
s: Scanner{},
@@ -84,8 +120,7 @@ func TestPostman_FromChunk(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Scanner{}
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Postman.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
@@ -94,9 +129,12 @@ func TestPostman_FromChunk(t *testing.T) {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
got[i].Raw = nil
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Errorf("Postman.FromData() error = %v, wantErr %v", got[i].VerificationError, tt.wantVerificationErr)
}
}
if diff := pretty.Compare(got, tt.want); diff != "" {
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Postman.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
+108
View File
@@ -0,0 +1,108 @@
package ramp
import (
"context"
"fmt"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
"net/http"
"net/url"
"regexp"
"strings"
)
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
keyPat = regexp.MustCompile(`\b(ramp_id_[[:alnum:]]{40})\b`)
secretPat = regexp.MustCompile(`\b(ramp_sec_[[:alnum:]]{48})\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"ramp_"}
}
// FromData will find and optionally verify Ramp secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
secretMatches := secretPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 2 {
continue
}
resMatch := strings.TrimSpace(match[1])
for _, secretMatch := range secretMatches {
if len(secretMatch) != 2 {
continue
}
resSecret := strings.TrimSpace(secretMatch[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Ramp,
Raw: []byte(resMatch),
RawV2: []byte(resMatch + ":" + resSecret),
}
if verify {
client := s.client
if client == nil {
client = defaultClient
}
body := url.Values{
"grant_type": {"client_credentials"},
"scope": {"user:read"},
}
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.ramp.com/developer/v1/token", strings.NewReader(body.Encode()))
if err != nil {
continue
}
req.SetBasicAuth(resMatch, resSecret)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 401 {
// The secret is determinately not verified (nothing to do)
} else {
s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
} else {
s1.VerificationError = err
}
}
// This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key.
if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, s1)
}
}
return results, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Ramp
}
+163
View File
@@ -0,0 +1,163 @@
//go:build detectors
// +build detectors
package ramp
import (
"context"
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestRamp_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
id := testSecrets.MustGetField("RAMP")
secret := testSecrets.MustGetField("RAMP_SECRET")
inactiveId := testSecrets.MustGetField("RAMP_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a ramp client id %s and a ramp secret %s within", id, secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Ramp,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a ramp id %s and ramp secret %s within but not valid", inactiveId, secret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Ramp,
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 ramp id %s and ramp secret %s within", id, secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Ramp,
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 ramp id %s and secret %s within", id, secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Ramp,
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("Ramp.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Ramp.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+3
View File
@@ -61,6 +61,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Sendbird,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/sendbird/",
}
if verify {
req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("https://api-%s.sendbird.com/v3/users", resAppIdMatch), nil)
+3
View File
@@ -48,6 +48,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_SendGrid,
Raw: []byte(res),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/sendgrid/",
}
if verify {
// there are a few endpoints we can check, but templates seems the least sensitive.
+3
View File
@@ -60,6 +60,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Slack,
Raw: []byte(token),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/slack/",
}
if verify {
client := s.client
if s.client == nil {
@@ -49,6 +49,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_SlackWebhook,
Raw: []byte(resMatch),
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/slack-webhook/",
}
if verify {
@@ -0,0 +1,91 @@
package sourcegraphcody
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
type Scanner struct {
client *http.Client
}
// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)
var (
defaultClient = common.SaneHttpClient()
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
keyPat = regexp.MustCompile(`\b(slk_[a-f0-9]{64})\b`)
)
// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
return []string{"slk_"}
}
// FromData will find and optionally verify Sourcegraphcody secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
dataStr := string(data)
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
for _, match := range matches {
if len(match) != 2 {
continue
}
resMatch := strings.TrimSpace(match[1])
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Raw: []byte(resMatch),
}
if verify {
client := s.client
if client == nil {
client = defaultClient
}
req, err := http.NewRequestWithContext(ctx, "GET", "https://cody-gateway.sourcegraph.com/v1/limits", nil)
req.Header.Add("Authorization", "Bearer "+resMatch)
if err != nil {
continue
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
} else if res.StatusCode == 403 || res.StatusCode == 401 {
// 403 when the gateway is not configured to accept the token,
// 401 when the token is invalid.
// The secret is determinately not verified (nothing to do)
} else {
s1.VerificationError = fmt.Errorf("unexpected HTTP response status %d", res.StatusCode)
}
} else {
s1.VerificationError = err
}
}
// This function will check false positives for common test words, but also it will make sure the key appears 'random' enough to be a real key.
if !s1.Verified && detectors.IsKnownFalsePositive(resMatch, detectors.DefaultFalsePositives, true) {
continue
}
results = append(results, s1)
}
return results, nil
}
func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_SourcegraphCody
}
@@ -0,0 +1,162 @@
//go:build detectors
// +build detectors
package sourcegraphcody
import (
"context"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)
func TestSourcegraphcody_FromChunk(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("SOURCEGRAPHCODY")
inactiveSecret := testSecrets.MustGetField("SOURCEGRAPHCODY_INACTIVE")
type args struct {
ctx context.Context
data []byte
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found, verified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Verified: true,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within but not valid", inactiveSecret)), // the secret would satisfy the regex but not pass validation
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: false,
},
{
name: "not found",
s: Scanner{},
args: args{
ctx: context.Background(),
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, would be verified if not for timeout",
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
{
name: "found, verified but unexpected api surface",
s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a sourcegraphcody secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_SourcegraphCody,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Sourcegraphcody.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
if (got[i].VerificationError != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError)
}
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Sourcegraphcody.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
}
func BenchmarkFromData(benchmark *testing.B) {
ctx := context.Background()
s := Scanner{}
for name, data := range detectors.MustGetBenchmarkData() {
benchmark.Run(name, func(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, err := s.FromData(ctx, false, data)
if err != nil {
b.Fatal(err)
}
}
})
}
}
+3
View File
@@ -49,6 +49,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Square,
Raw: []byte(res),
}
s.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/square/",
}
if verify {
// there are a few endpoints we can check, but templates seems the least sensitive.
+3
View File
@@ -40,6 +40,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
DetectorType: detectorspb.DetectorType_Stripe,
Raw: []byte(match),
}
s.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/stripe/",
}
if verify {
+5 -2
View File
@@ -23,7 +23,6 @@ var (
identifierPat = regexp.MustCompile(`(?i)sid.{0,20}AC[0-9a-f]{32}`) // Should we have this? Seems restrictive.
sidPat = regexp.MustCompile(`\bAC[0-9a-f]{32}\b`)
keyPat = regexp.MustCompile(`\b[0-9a-f]{32}\b`)
client = common.SaneHttpClient()
)
// Keywords are used for efficiently pre-filtering chunks.
@@ -54,8 +53,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
Redacted: sid,
}
s1.ExtraData = map[string]string{
"rotation_guide": "https://howtorotate.com/docs/tutorials/twilio/",
}
if verify {
client = s.client
client := s.client
if client == nil {
client = defaultClient
}
+6
View File
@@ -1,11 +1,14 @@
package engine
import (
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/anthropic"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/envoyapikey"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/huggingface"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/ramp"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/salesforce"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/snowflake"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sourcegraph"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/sourcegraphcody"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/tailscale"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors/trufflehogenterprise"
@@ -1548,6 +1551,9 @@ func DefaultDetectors() []detectors.Detector {
sourcegraph.Scanner{},
tailscale.Scanner{},
web3storage.Scanner{},
&ramp.Scanner{},
&anthropic.Scanner{},
&sourcegraphcody.Scanner{},
}
}
+18 -1
View File
@@ -60,7 +60,9 @@ type Engine struct {
// filterUnverified is used to reduce the number of unverified results.
// If there are multiple unverified results for the same chunk for the same detector,
// only the first one will be kept.
filterUnverified bool
filterUnverified bool
// entropyFilter is used to filter out unverified results using Shannon entropy.
filterEntropy *float64
onlyVerified bool
printAvgDetectorTime bool
@@ -128,6 +130,15 @@ func WithFilterUnverified(filter bool) EngineOption {
}
}
// WithFilterEntropy filters out unverified results using Shannon entropy.
func WithFilterEntropy(entropy float64) EngineOption {
return func(e *Engine) {
if entropy > 0 {
e.filterEntropy = &entropy
}
}
}
// WithOnlyVerified sets the onlyVerified flag on the engine. If set to true,
// the engine will only print verified results.
func WithOnlyVerified(onlyVerified bool) EngineOption {
@@ -295,6 +306,7 @@ func Start(ctx context.Context, options ...EngineOption) (*Engine, error) {
e.sourceManager = sources.NewManager(
sources.WithConcurrentSources(int(e.concurrency)),
sources.WithConcurrentUnits(int(e.concurrency)),
sources.WithSourceUnits(),
)
if len(e.decoders) == 0 {
@@ -513,6 +525,7 @@ func (e *Engine) detectChunk(ctx context.Context, data detectableChunk) {
if err != nil {
ctx.Logger().Error(err, "error scanning chunk")
}
if e.printAvgDetectorTime && len(results) > 0 {
elapsed := time.Since(start)
detectorName := results[0].DetectorType.String()
@@ -532,6 +545,10 @@ func (e *Engine) detectChunk(ctx context.Context, data detectableChunk) {
results = detectors.CleanResults(results)
}
if e.filterEntropy != nil {
results = detectors.FilterResultsWithEntropy(results, *e.filterEntropy)
}
for _, res := range results {
e.processResult(ctx, data, res)
}
+38
View File
@@ -0,0 +1,38 @@
package engine
import (
"runtime"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources/travisci"
)
// ScanTravisCI scans TravisCI logs.
func (e *Engine) ScanTravisCI(ctx context.Context, token string) error {
connection := &sourcespb.TravisCI{
Credential: &sourcespb.TravisCI_Token{
Token: token,
},
}
var conn anypb.Any
err := anypb.MarshalFrom(&conn, connection, proto.MarshalOptions{})
if err != nil {
ctx.Logger().Error(err, "failed to marshal Travis CI connection")
return err
}
sourceName := "trufflehog - Travis CI"
sourceID, jobID, _ := e.sourceManager.GetIDs(ctx, sourceName, travisci.SourceType)
travisSource := &travisci.Source{}
if err := travisSource.Init(ctx, sourceName, jobID, sourceID, true, &conn, runtime.NumCPU()); err != nil {
return err
}
_, err = e.sourceManager.Run(ctx, sourceName, travisSource)
return err
}
+31 -7
View File
@@ -1002,6 +1002,12 @@ const (
DetectorType_Sourcegraph DetectorType = 928
DetectorType_Tailscale DetectorType = 929
DetectorType_Web3Storage DetectorType = 930
DetectorType_AzureStorage DetectorType = 931
DetectorType_PlanetScaleDb DetectorType = 932
DetectorType_Anthropic DetectorType = 933
DetectorType_Ramp DetectorType = 934
DetectorType_Klaviyo DetectorType = 935
DetectorType_SourcegraphCody DetectorType = 936
)
// Enum value maps for DetectorType.
@@ -1934,6 +1940,12 @@ var (
928: "Sourcegraph",
929: "Tailscale",
930: "Web3Storage",
931: "AzureStorage",
932: "PlanetScaleDb",
933: "Anthropic",
934: "Ramp",
935: "Klaviyo",
936: "SourcegraphCody",
}
DetectorType_value = map[string]int32{
"Alibaba": 0,
@@ -2863,6 +2875,12 @@ var (
"Sourcegraph": 928,
"Tailscale": 929,
"Web3Storage": 930,
"AzureStorage": 931,
"PlanetScaleDb": 932,
"Anthropic": 933,
"Ramp": 934,
"Klaviyo": 935,
"SourcegraphCody": 936,
}
)
@@ -3241,7 +3259,7 @@ var file_detectors_proto_rawDesc = []byte{
0x44, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55,
0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x50, 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, 0x2a, 0xc0, 0x74, 0x0a, 0x0c, 0x44,
0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x2a, 0xa6, 0x75, 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,
@@ -4173,12 +4191,18 @@ var file_detectors_proto_rawDesc = []byte{
0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x10, 0x9f, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x53, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x10, 0xa0, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x54,
0x61, 0x69, 0x6c, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x10, 0xa1, 0x07, 0x12, 0x10, 0x0a, 0x0b, 0x57,
0x65, 0x62, 0x33, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x10, 0xa2, 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,
0x65, 0x62, 0x33, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x10, 0xa2, 0x07, 0x12, 0x11, 0x0a,
0x0c, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x10, 0xa3, 0x07,
0x12, 0x12, 0x0a, 0x0d, 0x50, 0x6c, 0x61, 0x6e, 0x65, 0x74, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x44,
0x62, 0x10, 0xa4, 0x07, 0x12, 0x0e, 0x0a, 0x09, 0x41, 0x6e, 0x74, 0x68, 0x72, 0x6f, 0x70, 0x69,
0x63, 0x10, 0xa5, 0x07, 0x12, 0x09, 0x0a, 0x04, 0x52, 0x61, 0x6d, 0x70, 0x10, 0xa6, 0x07, 0x12,
0x0c, 0x0a, 0x07, 0x4b, 0x6c, 0x61, 0x76, 0x69, 0x79, 0x6f, 0x10, 0xa7, 0x07, 0x12, 0x14, 0x0a,
0x0f, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x67, 0x72, 0x61, 0x70, 0x68, 0x43, 0x6f, 0x64, 0x79,
0x10, 0xa8, 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 (
File diff suppressed because it is too large Load Diff
@@ -486,6 +486,117 @@ var _ interface {
ErrorName() string
} = CircleCIValidationError{}
// Validate checks the field values on TravisCI with the rules defined in the
// proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *TravisCI) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on TravisCI with the rules defined in
// the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in TravisCIMultiError, or nil
// if none found.
func (m *TravisCI) ValidateAll() error {
return m.validate(true)
}
func (m *TravisCI) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
// no validation rules for Username
// no validation rules for Repository
// no validation rules for BuildNumber
// no validation rules for JobNumber
// no validation rules for Link
// no validation rules for Public
if len(errors) > 0 {
return TravisCIMultiError(errors)
}
return nil
}
// TravisCIMultiError is an error wrapping multiple validation errors returned
// by TravisCI.ValidateAll() if the designated constraints aren't met.
type TravisCIMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m TravisCIMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m TravisCIMultiError) AllErrors() []error { return m }
// TravisCIValidationError is the validation error returned by
// TravisCI.Validate if the designated constraints aren't met.
type TravisCIValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e TravisCIValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e TravisCIValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e TravisCIValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e TravisCIValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e TravisCIValidationError) ErrorName() string { return "TravisCIValidationError" }
// Error satisfies the builtin error interface
func (e TravisCIValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sTravisCI.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = TravisCIValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = TravisCIValidationError{}
// Validate checks the field values on Confluence with the rules defined in the
// proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
@@ -3926,6 +4037,37 @@ func (m *MetaData) validate(all bool) error {
}
}
case *MetaData_TravisCI:
if all {
switch v := interface{}(m.GetTravisCI()).(type) {
case interface{ ValidateAll() error }:
if err := v.ValidateAll(); err != nil {
errors = append(errors, MetaDataValidationError{
field: "TravisCI",
reason: "embedded message failed validation",
cause: err,
})
}
case interface{ Validate() error }:
if err := v.Validate(); err != nil {
errors = append(errors, MetaDataValidationError{
field: "TravisCI",
reason: "embedded message failed validation",
cause: err,
})
}
}
} else if v, ok := interface{}(m.GetTravisCI()).(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return MetaDataValidationError{
field: "TravisCI",
reason: "embedded message failed validation",
cause: err,
}
}
}
}
if len(errors) > 0 {
File diff suppressed because it is too large Load Diff
+122
View File
@@ -667,6 +667,124 @@ var _ interface {
ErrorName() string
} = CircleCIValidationError{}
// Validate checks the field values on TravisCI with the rules defined in the
// proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
func (m *TravisCI) Validate() error {
return m.validate(false)
}
// ValidateAll checks the field values on TravisCI with the rules defined in
// the proto definition for this message. If any rules are violated, the
// result is a list of violation errors wrapped in TravisCIMultiError, or nil
// if none found.
func (m *TravisCI) ValidateAll() error {
return m.validate(true)
}
func (m *TravisCI) validate(all bool) error {
if m == nil {
return nil
}
var errors []error
if _, err := url.Parse(m.GetEndpoint()); err != nil {
err = TravisCIValidationError{
field: "Endpoint",
reason: "value must be a valid URI",
cause: err,
}
if !all {
return err
}
errors = append(errors, err)
}
switch m.Credential.(type) {
case *TravisCI_Token:
// no validation rules for Token
}
if len(errors) > 0 {
return TravisCIMultiError(errors)
}
return nil
}
// TravisCIMultiError is an error wrapping multiple validation errors returned
// by TravisCI.ValidateAll() if the designated constraints aren't met.
type TravisCIMultiError []error
// Error returns a concatenation of all the error messages it wraps.
func (m TravisCIMultiError) Error() string {
var msgs []string
for _, err := range m {
msgs = append(msgs, err.Error())
}
return strings.Join(msgs, "; ")
}
// AllErrors returns a list of validation violation errors.
func (m TravisCIMultiError) AllErrors() []error { return m }
// TravisCIValidationError is the validation error returned by
// TravisCI.Validate if the designated constraints aren't met.
type TravisCIValidationError struct {
field string
reason string
cause error
key bool
}
// Field function returns field value.
func (e TravisCIValidationError) Field() string { return e.field }
// Reason function returns reason value.
func (e TravisCIValidationError) Reason() string { return e.reason }
// Cause function returns cause value.
func (e TravisCIValidationError) Cause() error { return e.cause }
// Key function returns key value.
func (e TravisCIValidationError) Key() bool { return e.key }
// ErrorName returns error name.
func (e TravisCIValidationError) ErrorName() string { return "TravisCIValidationError" }
// Error satisfies the builtin error interface
func (e TravisCIValidationError) Error() string {
cause := ""
if e.cause != nil {
cause = fmt.Sprintf(" | caused by: %v", e.cause)
}
key := ""
if e.key {
key = "key for "
}
return fmt.Sprintf(
"invalid %sTravisCI.%s: %s%s",
key,
e.field,
e.reason,
cause)
}
var _ error = TravisCIValidationError{}
var _ interface {
Field() string
Reason() string
Key() bool
Cause() error
ErrorName() string
} = TravisCIValidationError{}
// Validate checks the field values on Confluence with the rules defined in the
// proto definition for this message. If any rules are violated, the first
// error encountered is returned, or nil if there are no violations.
@@ -2202,6 +2320,8 @@ func (m *JIRA) validate(all bool) error {
errors = append(errors, err)
}
// no validation rules for InsecureSkipVerifyTls
switch m.Credential.(type) {
case *JIRA_BasicAuth:
@@ -3442,6 +3562,8 @@ func (m *Jenkins) validate(all bool) error {
errors = append(errors, err)
}
// no validation rules for InsecureSkipVerifyTls
switch m.Credential.(type) {
case *Jenkins_BasicAuth:
+20 -7
View File
@@ -34,7 +34,7 @@ func Chunker(originalChunk *Chunk) chan *Chunk {
chunkBytes := make([]byte, TotalChunkSize)
chunk := *originalChunk
chunkBytes = chunkBytes[:ChunkSize]
n, err := reader.Read(chunkBytes)
n, err := io.ReadFull(reader, chunkBytes)
if n > 0 {
peekData, _ := reader.Peek(TotalChunkSize - n)
chunkBytes = append(chunkBytes[:n], peekData...)
@@ -135,7 +135,7 @@ func readInChunks(ctx context.Context, reader io.Reader, config *chunkReaderConf
chunkRes := ChunkResult{}
chunkBytes := make([]byte, config.totalSize)
chunkBytes = chunkBytes[:config.chunkSize]
n, err := chunkReader.Read(chunkBytes)
n, err := io.ReadFull(chunkReader, chunkBytes)
if n > 0 {
peekData, _ := chunkReader.Peek(config.totalSize - n)
chunkBytes = append(chunkBytes[:n], peekData...)
@@ -143,14 +143,16 @@ func readInChunks(ctx context.Context, reader io.Reader, config *chunkReaderConf
}
// If there is an error other than EOF, or if we have read some bytes, send the chunk.
if err != nil && !errors.Is(err, io.EOF) || n > 0 {
if err != nil && !errors.Is(err, io.EOF) {
ctx.Logger().Error(err, "error reading chunk")
chunkRes.err = err
}
// io.ReadFull will only return io.EOF when n == 0.
if isErrAndNotEOF(err) {
ctx.Logger().Error(err, "error reading chunk")
chunkRes.err = err
chunkResultChan <- chunkRes
} else if n > 0 {
chunkResultChan <- chunkRes
}
// Return on any type of error.
if err != nil {
return
}
@@ -158,3 +160,14 @@ func readInChunks(ctx context.Context, reader io.Reader, config *chunkReaderConf
}()
return chunkResultChan
}
// reportableErr checks whether the error is one we are interested in flagging.
func isErrAndNotEOF(err error) bool {
if err == nil {
return false
}
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) {
return false
}
return true
}
+21
View File
@@ -5,6 +5,7 @@ import (
"io"
"strings"
"testing"
"testing/iotest"
diskbufferreader "github.com/bill-rich/disk-buffer-reader"
"github.com/stretchr/testify/assert"
@@ -196,3 +197,23 @@ func BenchmarkChunkReader(b *testing.B) {
assert.Nil(b, err)
}
}
func TestFlakyChunkReader(t *testing.T) {
a := "aaaa"
b := "bbbb"
reader := iotest.OneByteReader(strings.NewReader(a + b))
chunkReader := NewChunkReader()
chunkResChan := chunkReader(context.TODO(), reader)
var chunks []ChunkResult
for chunk := range chunkResChan {
chunks = append(chunks, chunk)
}
assert.Equal(t, 1, len(chunks))
chunk := chunks[0]
assert.NoError(t, chunk.Error())
assert.Equal(t, a+b, string(chunk.Bytes()))
}
+2 -2
View File
@@ -1,8 +1,8 @@
package sources
import (
"errors"
"fmt"
"reflect"
"sync"
"testing"
)
@@ -40,7 +40,7 @@ func TestNewScanErrors(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
got := NewScanErrors()
if !reflect.DeepEqual(got, tc.want) {
if !errors.Is(got.Errors(), tc.want.Errors()) {
t.Errorf("got %+v, want %+v", got, tc.want)
}
})
+1 -2
View File
@@ -39,8 +39,7 @@ type Source struct {
// Ensure the Source satisfies the interfaces at compile time
var _ sources.Source = (*Source)(nil)
var _ sources.SourceUnitUnmarshaller = (*Source)(nil)
var _ sources.SourceUnitEnumerator = (*Source)(nil)
var _ sources.SourceUnitChunker = (*Source)(nil)
var _ sources.SourceUnitEnumChunker = (*Source)(nil)
// Type returns the type of source.
// It is used for matching source types in configuration and job input.
+195 -10
View File
@@ -15,6 +15,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
"github.com/trufflesecurity/trufflehog/v3/pkg/sourcestest"
)
func TestSource_Scan(t *testing.T) {
@@ -84,23 +85,15 @@ func TestSource_Scan(t *testing.T) {
}
func TestScanFile(t *testing.T) {
tmpfile, err := os.CreateTemp("", "example.txt")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
chunkSize := sources.ChunkSize
secretPart1 := "SECRET"
secretPart2 := "SPLIT"
// Split the secret into two parts and pad the rest of the chunk with A's.
data := strings.Repeat("A", chunkSize-len(secretPart1)) + secretPart1 + secretPart2 + strings.Repeat("A", chunkSize-len(secretPart2))
_, err = tmpfile.Write([]byte(data))
assert.Nil(t, err)
err = tmpfile.Close()
tmpfile, cleanup, err := createTempFile("", data)
assert.Nil(t, err)
defer cleanup()
source := &Source{}
chunksChan := make(chan *sources.Chunk, 2)
@@ -120,3 +113,195 @@ func TestScanFile(t *testing.T) {
assert.Contains(t, foundSecret, secretPart1+secretPart2)
}
func TestEnumerate(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Setup the connection to test enumeration.
units := []string{
"/one", "/two", "/three",
"/path/to/dir/", "/path/to/another/dir/",
}
conn, err := anypb.New(&sourcespb.Filesystem{
Paths: units[0:3],
Directories: units[3:],
})
assert.NoError(t, err)
// Initialize the source.
s := Source{}
err = s.Init(ctx, "test enumerate", 0, 0, true, conn, 1)
assert.NoError(t, err)
reporter := sourcestest.TestReporter{}
err = s.Enumerate(ctx, &reporter)
assert.NoError(t, err)
assert.Equal(t, len(units), len(reporter.Units))
assert.Equal(t, 0, len(reporter.UnitErrs))
for _, unit := range reporter.Units {
assert.Contains(t, units, unit.SourceUnitID())
}
for _, unit := range units {
assert.Contains(t, reporter.Units, sources.CommonSourceUnit{ID: unit})
}
}
func TestChunkUnit(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Setup test file to chunk.
fileContents := "TestChunkUnit"
tmpfile, cleanup, err := createTempFile("", fileContents)
assert.NoError(t, err)
defer cleanup()
tmpdir, cleanup, err := createTempDir("", "foo", "bar", "baz")
assert.NoError(t, err)
defer cleanup()
conn, err := anypb.New(&sourcespb.Filesystem{})
assert.NoError(t, err)
// Initialize the source.
s := Source{}
err = s.Init(ctx, "test chunk unit", 0, 0, true, conn, 1)
assert.NoError(t, err)
// Happy path single file.
reporter := sourcestest.TestReporter{}
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: tmpfile.Name(),
}, &reporter)
assert.NoError(t, err)
// Happy path directory.
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: tmpdir,
}, &reporter)
assert.NoError(t, err)
// Error path.
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: "/file/not/found",
}, &reporter)
assert.NoError(t, err)
assert.Equal(t, 4, len(reporter.Chunks))
assert.Equal(t, 1, len(reporter.ChunkErrs))
dataFound := make(map[string]struct{}, 4)
for _, chunk := range reporter.Chunks {
dataFound[string(chunk.Data)] = struct{}{}
}
assert.Contains(t, dataFound, fileContents)
assert.Contains(t, dataFound, "foo")
assert.Contains(t, dataFound, "bar")
assert.Contains(t, dataFound, "baz")
}
func TestEnumerateReporterErr(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Setup the connection to test enumeration.
units := []string{
"/one", "/two", "/three",
"/path/to/dir/", "/path/to/another/dir/",
}
conn, err := anypb.New(&sourcespb.Filesystem{
Paths: units[0:3],
Directories: units[3:],
})
assert.NoError(t, err)
// Initialize the source.
s := Source{}
err = s.Init(ctx, "test enumerate", 0, 0, true, conn, 1)
assert.NoError(t, err)
// Enumerate should always return an error if the reporter returns an
// error.
reporter := sourcestest.ErrReporter{}
err = s.Enumerate(ctx, &reporter)
assert.Error(t, err)
}
func TestChunkUnitReporterErr(t *testing.T) {
t.Parallel()
ctx := context.Background()
// Setup test file to chunk.
tmpfile, err := os.CreateTemp("", "example.txt")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name())
fileContents := []byte("TestChunkUnit")
_, err = tmpfile.Write(fileContents)
assert.NoError(t, err)
assert.NoError(t, tmpfile.Close())
conn, err := anypb.New(&sourcespb.Filesystem{})
assert.NoError(t, err)
// Initialize the source.
s := Source{}
err = s.Init(ctx, "test chunk unit", 0, 0, true, conn, 1)
assert.NoError(t, err)
// Happy path. ChunkUnit should always return an error if the reporter
// returns an error.
reporter := sourcestest.ErrReporter{}
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: tmpfile.Name(),
}, &reporter)
assert.Error(t, err)
// Error path. ChunkUnit should always return an error if the reporter
// returns an error.
err = s.ChunkUnit(ctx, sources.CommonSourceUnit{
ID: "/file/not/found",
}, &reporter)
assert.Error(t, err)
}
// createTempFile is a helper function to create a temporary file in the given
// directory with the provided contents. If dir is "", the operating system's
// temp directory is used.
func createTempFile(dir string, contents string) (*os.File, func(), error) {
tmpfile, err := os.CreateTemp(dir, "trufflehogtest")
if err != nil {
return nil, nil, err
}
if _, err := tmpfile.Write([]byte(contents)); err != nil {
_ = os.Remove(tmpfile.Name())
return nil, nil, err
}
if err := tmpfile.Close(); err != nil {
_ = os.Remove(tmpfile.Name())
return nil, nil, err
}
return tmpfile, func() { _ = os.Remove(tmpfile.Name()) }, nil
}
// createTempDir is a helper function to create a temporary directory in the
// given directory with files containing the provided contents. If dir is "",
// the operating system's temp directory is used.
func createTempDir(dir string, contents ...string) (string, func(), error) {
tmpdir, err := os.MkdirTemp(dir, "trufflehogtest")
if err != nil {
return "", nil, err
}
for _, content := range contents {
if _, _, err := createTempFile(tmpdir, content); err != nil {
_ = os.RemoveAll(tmpdir)
return "", nil, err
}
}
return tmpdir, func() { _ = os.RemoveAll(tmpdir) }, nil
}
+1 -1
View File
@@ -222,7 +222,7 @@ func (s *Source) Init(aCtx context.Context, name string, jobID sources.JobID, so
}
s.conn = &conn
s.filteredRepoCache = s.newFilteredRepoCache(memory.New(), s.conn.IncludeRepos, s.conn.IgnoreRepos)
s.filteredRepoCache = s.newFilteredRepoCache(memory.New(), s.conn.GetRepositories(), s.conn.GetIgnoreRepos())
s.memberCache = make(map[string]struct{})
s.repoSizes = newRepoSize()
+2 -2
View File
@@ -78,7 +78,7 @@ func TestAddReposByOrg(t *testing.T) {
Credential: &sourcespb.GitHub_Token{
Token: "super secret token",
},
IncludeRepos: nil,
Repositories: nil,
IgnoreRepos: []string{"secret/super-*-repo2"},
})
// gock works here because github.NewClient is using the default HTTP Transport
@@ -106,7 +106,7 @@ func TestAddReposByOrg_IncludeRepos(t *testing.T) {
Credential: &sourcespb.GitHub_Token{
Token: "super secret token",
},
IncludeRepos: []string{"secret/super*"},
Repositories: []string{"secret/super*"},
Organizations: []string{"super-secret-org"},
})
// gock works here because github.NewClient is using the default HTTP Transport
+1 -1
View File
@@ -120,7 +120,7 @@ func TestJobProgressElapsedTime(t *testing.T) {
metrics := JobProgressMetrics{}
assert.Equal(t, time.Duration(0), metrics.ElapsedTime())
metrics.StartTime = time.Now()
metrics.StartTime = time.Date(2022, time.March, 30, 0, 0, 0, 0, time.UTC)
assert.Greater(t, metrics.ElapsedTime(), time.Duration(0))
metrics.EndTime = metrics.StartTime.Add(1 * time.Hour)
+223
View File
@@ -0,0 +1,223 @@
package travisci
import (
"fmt"
"strconv"
"github.com/go-errors/errors"
"github.com/shuheiktgw/go-travis"
"golang.org/x/sync/errgroup"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
)
const (
SourceType = sourcespb.SourceType_SOURCE_TYPE_TRAVISCI
baseURL = "https://api.travis-ci.com/"
pageSize = 100
)
type Source struct {
name string
sourceId sources.SourceID
jobId sources.JobID
verify bool
jobPool *errgroup.Group
sources.Progress
client *travis.Client
sources.CommonSourceUnitUnmarshaller
returnAfterFirstChunk bool
}
// Ensure the Source satisfies the interfaces at compile time.
var _ sources.Source = (*Source)(nil)
var _ sources.SourceUnitUnmarshaller = (*Source)(nil)
// Type returns the type of source.
// It is used for matching source types in configuration and job input.
func (s *Source) Type() sourcespb.SourceType {
return SourceType
}
func (s *Source) SourceID() sources.SourceID {
return s.sourceId
}
func (s *Source) JobID() sources.JobID {
return s.jobId
}
// Init returns an initialized TravisCI source.
func (s *Source) Init(ctx context.Context, name string, jobId sources.JobID, sourceId sources.SourceID, verify bool, connection *anypb.Any, concurrency int) error {
s.name = name
s.sourceId = sourceId
s.jobId = jobId
s.verify = verify
s.jobPool = &errgroup.Group{}
s.jobPool.SetLimit(concurrency)
var conn sourcespb.TravisCI
if err := anypb.UnmarshalTo(connection, &conn, proto.UnmarshalOptions{}); err != nil {
return errors.WrapPrefix(err, "error unmarshalling connection", 0)
}
switch conn.Credential.(type) {
case *sourcespb.TravisCI_Token:
if conn.GetToken() == "" {
return errors.New("token is empty")
}
s.client = travis.NewClient(baseURL, conn.GetToken())
s.client.HTTPClient = common.RetryableHttpClientTimeout(3)
user, _, err := s.client.User.Current(ctx, nil)
if err != nil {
return errors.WrapPrefix(err, "error getting testing travis client", 0)
}
ctx.Logger().V(2).Info("authenticated to Travis CI with user", "username", user.Login)
default:
return errors.New("credential type not implemented for Travis CI")
}
return nil
}
func (s *Source) Enumerate(ctx context.Context, reporter sources.UnitReporter) error {
for repoPage := 0; ; repoPage++ {
repositories, _, err := s.client.Repositories.List(ctx, &travis.RepositoriesOption{
Limit: pageSize,
Offset: repoPage * pageSize,
})
if err != nil {
if repoPage == 0 {
return fmt.Errorf("error listing repositories: %w", err)
}
err = reporter.UnitErr(ctx, err)
if err != nil {
return fmt.Errorf("error reporting error: %w", err)
}
}
if len(repositories) == 0 {
break
}
for _, repo := range repositories {
err = reporter.UnitOk(ctx, sources.CommonSourceUnit{
ID: strconv.Itoa(int(*repo.Id)),
})
if err != nil {
return fmt.Errorf("error reporting unit: %w", err)
}
ctx.Logger().V(2).Info("enumerated repository", "id", repo.Id, "name", repo.Name)
}
}
return nil
}
// ChunkUnit implements SourceUnitChunker interface.
func (s *Source) ChunkUnit(ctx context.Context, unit sources.SourceUnit, reporter sources.ChunkReporter) error {
repo, _, err := s.client.Repositories.Find(ctx, unit.SourceUnitID(), nil)
if err != nil {
return fmt.Errorf("error finding repository: %w", err)
}
logger := ctx.Logger().WithValues("repo", *repo.Name)
logger.V(2).Info("scanning repository")
// Counts continuous errors from ListByRepoSlug. Used to quit early in
// case the API always returns an error.
var buildPageErrs int
for buildPage := 0; ; buildPage++ {
builds, _, err := s.client.Builds.ListByRepoSlug(ctx, *repo.Slug, &travis.BuildsByRepoOption{
Limit: pageSize,
Offset: buildPage * pageSize,
})
if err != nil {
if err := reporter.ChunkErr(ctx, err); err != nil {
return err
}
buildPageErrs++
if buildPageErrs >= 5 {
return fmt.Errorf("encountered too many errors listing builds, aborting")
}
continue
}
// Reset the page error counter.
buildPageErrs = 0
if len(builds) == 0 {
break
}
for _, build := range builds {
jobs, _, err := s.client.Jobs.ListByBuild(ctx, *build.Id)
if err != nil {
if err := reporter.ChunkErr(ctx, err); err != nil {
return err
}
continue
}
if len(jobs) == 0 {
break
}
for _, job := range jobs {
log, _, err := s.client.Logs.FindByJobId(ctx, *job.Id)
if err != nil {
if err := reporter.ChunkErr(ctx, err); err != nil {
return err
}
continue
}
logger.V(3).Info("scanning job", "id", *job.Id, "number", *job.Number)
chunk := sources.Chunk{
SourceType: s.Type(),
SourceName: s.name,
SourceID: s.SourceID(),
JobID: s.JobID(),
Data: []byte(*log.Content),
SourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_TravisCI{
TravisCI: &source_metadatapb.TravisCI{
Username: *job.Owner.Login,
Repository: *repo.Name,
BuildNumber: *build.Number,
JobNumber: *job.Number,
Link: fmt.Sprintf("https://app.travis-ci.com/github/%s/%s/jobs/%d", *job.Owner.Login, *repo.Name, *job.Id),
Public: !*repo.Private,
},
},
},
Verify: s.verify,
}
if err := reporter.ChunkOk(ctx, chunk); err != nil {
return err
}
if s.returnAfterFirstChunk {
return nil
}
}
}
}
return nil
}
// Chunks emits chunks of bytes over a channel.
// It's a no-op because we've implemented the SourceUnitChunker interface instead.
func (s *Source) Chunks(ctx context.Context, chunksChan chan *sources.Chunk, _ ...sources.ChunkingTarget) error {
return nil
}
+99
View File
@@ -0,0 +1,99 @@
package travisci
import (
"fmt"
"testing"
"github.com/kylelemons/godebug/pretty"
"google.golang.org/protobuf/types/known/anypb"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/source_metadatapb"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
"github.com/trufflesecurity/trufflehog/v3/pkg/sourcestest"
)
func TestSource_Scan(t *testing.T) {
ctx := context.Background()
secret, err := common.GetTestSecret(ctx)
if err != nil {
t.Fatal(fmt.Errorf("failed to access secret: %v", err))
}
token := secret.MustGetField("TRAVISCI_TOKEN")
type init struct {
name string
verify bool
connection *sourcespb.TravisCI
}
tests := []struct {
name string
init init
wantSourceMetadata *source_metadatapb.MetaData
wantErr bool
}{
{
name: "get a chunk",
init: init{
name: "example repo",
connection: &sourcespb.TravisCI{
Credential: &sourcespb.TravisCI_Token{
Token: token,
},
},
verify: true,
},
wantSourceMetadata: &source_metadatapb.MetaData{
Data: &source_metadatapb.MetaData_TravisCI{
TravisCI: &source_metadatapb.TravisCI{
Username: "truffle-sandbox",
Repository: "test-repo",
BuildNumber: "1",
JobNumber: "1.1",
Link: "https://app.travis-ci.com/github/truffle-sandbox/test-repo/jobs/611053994",
Public: false,
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Source{}
conn, err := anypb.New(tt.init.connection)
if err != nil {
t.Fatal(err)
}
err = s.Init(ctx, tt.init.name, 0, 0, tt.init.verify, conn, 5)
if (err != nil) != tt.wantErr {
t.Fatalf("Source.Init() error = %v, wantErr %v", err, tt.wantErr)
}
reporter := sourcestest.TestReporter{}
s.returnAfterFirstChunk = true
err = s.Enumerate(ctx, &reporter)
if (err != nil) != tt.wantErr {
t.Fatalf("Source.Enumerate() error = %v, wantErr %v", err, tt.wantErr)
}
for _, unit := range reporter.Units {
err = s.ChunkUnit(ctx, unit, &reporter)
if (err != nil) != tt.wantErr {
t.Fatalf("Source.ChunkUnit() error = %v, wantErr %v", err, tt.wantErr)
}
}
for _, chunk := range reporter.Chunks {
if diff := pretty.Compare(chunk.SourceMetadata, tt.wantSourceMetadata); diff != "" {
t.Fatalf("comparing chunks: %s diff: (-got +want)\n%s", tt.name, diff)
}
}
})
}
}
+61
View File
@@ -0,0 +1,61 @@
package sourcestest
import (
"fmt"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/sources"
)
type reporter interface {
sources.UnitReporter
sources.ChunkReporter
}
var (
_ reporter = (*TestReporter)(nil)
_ reporter = (*ErrReporter)(nil)
)
// TestReporter is a helper struct that implements both UnitReporter and
// ChunkReporter by simply recording the values passed in the methods.
type TestReporter struct {
Units []sources.SourceUnit
UnitErrs []error
Chunks []sources.Chunk
ChunkErrs []error
}
func (t *TestReporter) UnitOk(_ context.Context, unit sources.SourceUnit) error {
t.Units = append(t.Units, unit)
return nil
}
func (t *TestReporter) UnitErr(_ context.Context, err error) error {
t.UnitErrs = append(t.UnitErrs, err)
return nil
}
func (t *TestReporter) ChunkOk(_ context.Context, chunk sources.Chunk) error {
t.Chunks = append(t.Chunks, chunk)
return nil
}
func (t *TestReporter) ChunkErr(_ context.Context, err error) error {
t.ChunkErrs = append(t.ChunkErrs, err)
return nil
}
// ErrReporter implements UnitReporter and ChunkReporter but always returns an
// error.
type ErrReporter struct{}
func (ErrReporter) UnitOk(context.Context, sources.SourceUnit) error {
return fmt.Errorf("ErrReporter: UnitOk error")
}
func (ErrReporter) UnitErr(context.Context, error) error {
return fmt.Errorf("ErrReporter: UnitErr error")
}
func (ErrReporter) ChunkOk(context.Context, sources.Chunk) error {
return fmt.Errorf("ErrReporter: ChunkOk error")
}
func (ErrReporter) ChunkErr(context.Context, error) error {
return fmt.Errorf("ErrReporter: ChunkErr error")
}
+4 -3
View File
@@ -4,6 +4,7 @@ import (
"strings"
"github.com/muesli/reflow/truncate"
"github.com/trufflesecurity/trufflehog/v3/pkg/tui/components/textinputs"
)
// TruncateString is a convenient wrapper around truncate.TruncateString.
@@ -14,11 +15,11 @@ func TruncateString(s string, max int) string {
return truncate.StringWithTail(s, uint(max), "…")
}
func SummarizeSource(keys []string, inputs map[string]string, labels map[string]string) string {
func SummarizeSource(keys []string, inputs map[string]textinputs.Input, labels map[string]string) string {
summary := strings.Builder{}
for _, key := range keys {
if inputs[key] != "" {
summary.WriteString("\t" + labels[key] + ": " + inputs[key] + "\n")
if inputs[key].Value != "" {
summary.WriteString("\t" + labels[key] + ": " + inputs[key].Value + "\n")
}
}
+14 -3
View File
@@ -47,11 +47,22 @@ type InputConfig struct {
Placeholder string
}
func (m Model) GetInputs() map[string]string {
inputs := make(map[string]string)
type Input struct {
Value string
IsDefault bool
}
func (m Model) GetInputs() map[string]Input {
inputs := make(map[string]Input)
for i, input := range m.inputs {
inputs[m.configs[i].Key] = input.Value()
isDefault := false
value := input.Value()
if value == "" && m.configs[i].Required {
isDefault = true
value = input.Placeholder
}
inputs[m.configs[i].Key] = Input{Value: value, IsDefault: isDefault}
}
return inputs
@@ -1,6 +1,7 @@
package source_configure
import (
"runtime"
"strconv"
"strings"
@@ -49,7 +50,7 @@ func GetTrufflehogConfiguration() truffleCmdModel {
Key: "concurrency",
Required: false,
Help: "Number of concurrent workers.",
Placeholder: "1",
Placeholder: strconv.Itoa(runtime.NumCPU()),
}
return truffleCmdModel{textinputs.New([]textinputs.InputConfig{jsonOutput, verification, verifiedResults, excludeDetectors, concurrency}).SetSkip(true)}
@@ -59,25 +60,25 @@ func (m truffleCmdModel) Cmd() string {
var command []string
inputs := m.GetInputs()
if isTrue(inputs["json"]) {
if isTrue(inputs["json"].Value) {
command = append(command, "--json")
}
if isTrue(inputs["no-verification"]) {
if isTrue(inputs["no-verification"].Value) {
command = append(command, "--no-verification")
}
if isTrue(inputs["only-verified"]) {
if isTrue(inputs["only-verified"].Value) {
command = append(command, "--only-verified")
}
if inputs["exclude_detectors"] != "" {
cmd := "--exclude-detectors=" + strings.ReplaceAll(inputs["exclude_detectors"], " ", "")
if inputs["exclude_detectors"].Value != "" {
cmd := "--exclude-detectors=" + strings.ReplaceAll(inputs["exclude_detectors"].Value, " ", "")
command = append(command, cmd)
}
if inputs["concurrency"] != "" {
command = append(command, "--concurrency="+inputs["concurrency"])
if inputs["concurrency"].Value != "" {
command = append(command, "--concurrency="+inputs["concurrency"].Value)
}
return strings.Join(command, " ")
@@ -90,8 +91,8 @@ func (m truffleCmdModel) Summary() string {
inputs := m.GetInputs()
labels := m.GetLabels()
for _, key := range keys {
if inputs[key] != "" {
summary.WriteString("\t" + labels[key] + ": " + inputs[key] + "\n")
if inputs[key].Value != "" {
summary.WriteString("\t" + labels[key] + ": " + inputs[key].Value + "\n")
}
}
@@ -73,6 +73,7 @@ func New(c common.Common) *SourceSelect {
OssItem("GCS (Google Cloud Storage)", "Scan a Google Cloud Storage instance."),
// Enterprise sources.
EnterpriseItem("Artifactory", "Scan JFrog Artifactory packages."),
EnterpriseItem("Azure Repos", "Scan Microsoft Azure repositories."),
EnterpriseItem("BitBucket", "Scan Atlassian's Git-based source code repository hosting service."),
EnterpriseItem("Buildkite", "Scan Buildkite, a CI/CD platform."),
EnterpriseItem("Confluence", "Scan Atlassian's web-based wiki and knowledge base."),
+1 -4
View File
@@ -27,10 +27,7 @@ func (m circleCiCmdModel) Cmd() string {
command = append(command, "trufflehog", "circleci")
inputs := m.GetInputs()
if inputs["token"] != "" {
command = append(command, "--token="+inputs["token"])
}
command = append(command, "--token="+inputs["token"].Value)
return strings.Join(command, " ")
}
+2 -1
View File
@@ -29,7 +29,8 @@ func (m dockerCmdModel) Cmd() string {
command = append(command, "trufflehog", "docker")
inputs := m.GetInputs()
vals := inputs["images"]
vals := inputs["images"].Value
if vals != "" {
images := strings.Fields(vals)
for _, image := range images {
+1 -4
View File
@@ -28,10 +28,7 @@ func (m fsModel) Cmd() string {
command = append(command, "trufflehog", "filesystem")
inputs := m.GetInputs()
if inputs["path"] != "" {
command = append(command, inputs["path"])
}
command = append(command, inputs["path"].Value)
return strings.Join(command, " ")
}
+5 -6
View File
@@ -14,9 +14,9 @@ type gcsCmdModel struct {
func GetFields() gcsCmdModel {
projectId := textinputs.InputConfig{
Label: "Project ID",
Key: "project_id",
Key: "project-id",
Required: true,
Placeholder: "my-project",
Placeholder: "trufflehog-testing",
}
return gcsCmdModel{textinputs.New([]textinputs.InputConfig{projectId})}
@@ -27,9 +27,8 @@ func (m gcsCmdModel) Cmd() string {
command = append(command, "trufflehog", "gcs")
inputs := m.GetInputs()
if inputs["project_id"] != "" {
command = append(command, "--project_id="+inputs["project_id"])
}
command = append(command, "--project-id="+inputs["project-id"].Value)
command = append(command, "--cloud-environment")
return strings.Join(command, " ")
@@ -39,6 +38,6 @@ func (m gcsCmdModel) Summary() string {
inputs := m.GetInputs()
labels := m.GetLabels()
keys := []string{"project_id"}
keys := []string{"project-id"}
return common.SummarizeSource(keys, inputs, labels)
}
+3 -4
View File
@@ -15,8 +15,9 @@ func GetFields() gitCmdModel {
uri := textinputs.InputConfig{
Label: "Git URI",
Key: "uri",
Help: "file:// for local git repos",
Required: true,
Placeholder: "git@github.com:trufflesecurity/trufflehog.git.",
Placeholder: "git@github.com:trufflesecurity/trufflehog.git",
}
return gitCmdModel{textinputs.New([]textinputs.InputConfig{uri})}
@@ -28,9 +29,7 @@ func (m gitCmdModel) Cmd() string {
inputs := m.GetInputs()
if inputs["uri"] != "" {
command = append(command, inputs["uri"])
}
command = append(command, inputs["uri"].Value)
return strings.Join(command, " ")
}
+23 -9
View File
@@ -19,7 +19,7 @@ func GetFields() githubCmdModel {
org := textinputs.InputConfig{
Label: "Organization",
Key: "org",
Required: false,
Required: true,
Help: "GitHub organization to scan.",
Placeholder: "trufflesecurity",
}
@@ -27,7 +27,7 @@ func GetFields() githubCmdModel {
repo := textinputs.InputConfig{
Label: "Repository",
Key: "repo",
Required: false,
Required: true,
Help: "GitHub repo to scan.",
Placeholder: "https://github.com/trufflesecurity/test_keys",
}
@@ -35,25 +35,39 @@ func GetFields() githubCmdModel {
return githubCmdModel{textinputs.New([]textinputs.InputConfig{org, repo})}
}
// Handle default values since GitHub flags are OR operations
func (m githubCmdModel) GetSpecialInputs() map[string]textinputs.Input {
inputs := m.GetInputs()
if inputs["org"].IsDefault != inputs["repo"].IsDefault {
if inputs["org"].IsDefault {
delete(inputs, "org")
}
if inputs["repo"].IsDefault {
delete(inputs, "repo")
}
}
return inputs
}
func (m githubCmdModel) Cmd() string {
var command []string
command = append(command, "trufflehog", "github")
inputs := m.GetSpecialInputs()
inputs := m.GetInputs()
if inputs["org"] != "" {
command = append(command, "--org="+inputs["org"])
if inputs["org"].Value != "" {
command = append(command, "--org="+inputs["org"].Value)
}
if inputs["repo"] != "" {
command = append(command, "--repo="+inputs["repo"])
if inputs["repo"].Value != "" {
command = append(command, "--repo="+inputs["repo"].Value)
}
return strings.Join(command, " ")
}
func (m githubCmdModel) Summary() string {
inputs := m.GetInputs()
inputs := m.GetSpecialInputs()
labels := m.GetLabels()
keys := []string{"org", "repo"}
+1 -3
View File
@@ -29,9 +29,7 @@ func (m gitlabCmdModel) Cmd() string {
inputs := m.GetInputs()
if inputs["token"] != "" {
command = append(command, "--token="+inputs["token"])
}
command = append(command, "--token="+inputs["token"].Value)
return strings.Join(command, " ")
}
+2 -2
View File
@@ -16,7 +16,7 @@ func GetFields() s3CmdModel {
Label: "S3 bucket name(s)",
Key: "buckets",
Required: true,
Placeholder: "my-bucket-name",
Placeholder: "truffletestbucket",
Help: "Buckets to scan. Separate by space if multiple.",
}
@@ -28,7 +28,7 @@ func (m s3CmdModel) Cmd() string {
command = append(command, "trufflehog", "s3")
inputs := m.GetInputs()
vals := inputs["buckets"]
vals := inputs["buckets"].Value
if vals != "" {
buckets := strings.Fields(vals)
for _, bucket := range buckets {
+2 -4
View File
@@ -64,10 +64,8 @@ func (m syslogCmdModel) Cmd() string {
syslogKeys := [5]string{"address", "protocol", "cert", "key", "format"}
for _, key := range syslogKeys {
if inputs[key] != "" {
flag := "--" + key + "=" + inputs[key]
command = append(command, flag)
}
flag := "--" + key + "=" + inputs[key].Value
command = append(command, flag)
}
return strings.Join(command, " ")
+6
View File
@@ -939,6 +939,12 @@ enum DetectorType {
Sourcegraph = 928;
Tailscale = 929;
Web3Storage = 930;
AzureStorage = 931;
PlanetScaleDb = 932;
Anthropic = 933;
Ramp = 934;
Klaviyo = 935;
SourcegraphCody = 936;
}
message Result {
+11
View File
@@ -44,6 +44,16 @@ message CircleCI {
string email = 7;
}
message TravisCI {
string username = 2;
string repository = 3;
string build_number = 4;
string job_number = 5;
string link = 6;
bool public = 7;
}
message Confluence {
string page = 1;
string space = 2;
@@ -294,5 +304,6 @@ message MetaData {
SharePoint sharepoint = 25;
GoogleDrive googleDrive = 26;
AzureRepos azureRepos = 27;
TravisCI travisCI = 28;
}
}
+12
View File
@@ -44,6 +44,7 @@ enum SourceType {
SOURCE_TYPE_SHAREPOINT = 29;
SOURCE_TYPE_GCS_UNAUTHED = 30;
SOURCE_TYPE_AZURE_REPOS = 31;
SOURCE_TYPE_TRAVISCI = 32;
}
message LocalSource {
@@ -83,6 +84,13 @@ message CircleCI {
}
}
message TravisCI {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
string token = 2;
}
}
message Confluence {
string endpoint = 1 [(validate.rules).string.uri_ref = true];
oneof credential {
@@ -204,6 +212,7 @@ message JIRA {
}
repeated string projects = 5;
repeated string ignore_projects = 7;
bool insecure_skip_verify_tls = 8;
}
message NPMUnauthenticatedPackage {
@@ -263,6 +272,7 @@ message Jenkins {
credentials.BasicAuth basic_auth = 2;
credentials.Header header = 3;
}
bool insecure_skip_verify_tls = 4;
}
message Teams {
@@ -285,6 +295,8 @@ message Artifactory {
string access_token = 3;
}
repeated string repositories = 4;
repeated string include_paths = 5;
repeated string ignore_paths = 6;
}
message Syslog {