Enigma detector enhance verification to prevent false verified findings (#5011)
* enhance the verification in the enigma detector * feat(enigma): gate detector behind feature flag
This commit is contained in:
@@ -532,6 +532,7 @@ func run(state overseer.State, logSync func() error) {
|
||||
feature.PineconeDetectorEnabled.Store(true)
|
||||
feature.CloudinaryDetectorEnabled.Store(true)
|
||||
feature.GitLabOAuthDetectorEnabled.Store(true)
|
||||
feature.EnigmaDetectorEnabled.Store(true)
|
||||
|
||||
conf := &config.Config{}
|
||||
if *configFilename != "" {
|
||||
|
||||
@@ -2,6 +2,8 @@ package enigma
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@@ -33,7 +35,6 @@ func (s Scanner) Keywords() []string {
|
||||
// FromData will find and optionally verify Enigma secrets in a given set of bytes.
|
||||
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
|
||||
dataStr := string(data)
|
||||
|
||||
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
|
||||
|
||||
for _, match := range matches {
|
||||
@@ -46,20 +47,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
}
|
||||
|
||||
if verify {
|
||||
payload := strings.NewReader(`{"name":"Enigma Technologies, Inc.","person":{"first_name":"","last_name":""},"address":{"street_address1":"245 5th Ave","street_address2":"","city":"New York","state":"NY","postal_code":"10016"}}`)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.enigma.com/businesses/match", payload)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("x-api-key", resMatch)
|
||||
res, err := client.Do(req)
|
||||
if err == nil {
|
||||
defer func() { _ = res.Body.Close() }()
|
||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
||||
s1.Verified = true
|
||||
}
|
||||
}
|
||||
isVerified, verificationErr := verifyKey(ctx, client, resMatch)
|
||||
s1.Verified = isVerified
|
||||
s1.SetVerificationError(verificationErr, resMatch)
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
@@ -68,10 +58,52 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// verifyKey checks an Enigma API key against the GraphQL endpoint.
|
||||
//
|
||||
// The query selects only `__typename`, which the GraphQL spec guarantees on
|
||||
// every root type, so verification does not depend on Enigma's evolving schema
|
||||
// or any specific entity id. Authentication is driven by the HTTP status code:
|
||||
// 200 means the key is valid, 401 mean it is not.
|
||||
//
|
||||
// A 200 is additionally guarded by a JSON Content-Type check: server could return
|
||||
// a 200 HTML block page, which we must not mistake for a live key.
|
||||
func verifyKey(ctx context.Context, client *http.Client, key string) (bool, error) {
|
||||
payload := strings.NewReader(`{"query":"{ __typename }"}`)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.enigma.com/graphql", payload)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("x-api-key", key)
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, res.Body)
|
||||
_ = res.Body.Close()
|
||||
}()
|
||||
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
if contentType := res.Header.Get("Content-Type"); !strings.Contains(contentType, "application/json") {
|
||||
return false, fmt.Errorf("got HTTP 200 with unexpected Content-Type %q", contentType)
|
||||
}
|
||||
|
||||
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() detector_typepb.DetectorType {
|
||||
return detector_typepb.DetectorType_Enigma
|
||||
}
|
||||
|
||||
func (s Scanner) Description() string {
|
||||
return "Enigma is a data intelligence company that provides comprehensive data about businesses. Enigma API keys can be used to access and interact with this data."
|
||||
return "Enigma is a business data intelligence platform. Enigma API keys grant access to comprehensive firmographic, legal, and identity datasets."
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
func TestEnigma_FromChunk(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors1")
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
@@ -50,6 +50,9 @@ func TestEnigma_FromChunk(t *testing.T) {
|
||||
{
|
||||
DetectorType: detector_typepb.DetectorType_Enigma,
|
||||
Verified: true,
|
||||
SecretParts: map[string]string{
|
||||
"key": secret,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
@@ -66,6 +69,9 @@ func TestEnigma_FromChunk(t *testing.T) {
|
||||
{
|
||||
DetectorType: detector_typepb.DetectorType_Enigma,
|
||||
Verified: false,
|
||||
SecretParts: map[string]string{
|
||||
"key": inactiveSecret,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
|
||||
@@ -1778,6 +1778,8 @@ func buildDetectorList() []detectors.Detector {
|
||||
return !feature.CloudinaryDetectorEnabled.Load()
|
||||
case *gitlaboauth2.Scanner:
|
||||
return !feature.GitLabOAuthDetectorEnabled.Load()
|
||||
case *enigma.Scanner:
|
||||
return !feature.EnigmaDetectorEnabled.Load()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ var excludedFromDefaultList = map[detector_typepb.DetectorType]struct{}{
|
||||
// Remove each entry once its detector has been carefully added.
|
||||
detector_typepb.DetectorType_Cloudinary: {},
|
||||
detector_typepb.DetectorType_DatadogApikey: {},
|
||||
detector_typepb.DetectorType_Enigma: {},
|
||||
detector_typepb.DetectorType_GitLabOauth2: {},
|
||||
detector_typepb.DetectorType_Guru: {},
|
||||
detector_typepb.DetectorType_IPInfo: {},
|
||||
|
||||
@@ -19,6 +19,7 @@ var (
|
||||
PineconeDetectorEnabled atomic.Bool
|
||||
CloudinaryDetectorEnabled atomic.Bool
|
||||
GitLabOAuthDetectorEnabled atomic.Bool
|
||||
EnigmaDetectorEnabled atomic.Bool
|
||||
)
|
||||
|
||||
type AtomicString struct {
|
||||
|
||||
Reference in New Issue
Block a user