Improve Copper Detector verification with stricter status code and email matching (#4594)
Lint / golangci-lint (push) Waiting to run
Lint / semgrep (push) Waiting to run
Release / Release (push) Waiting to run
Scan for secrets / test (push) Waiting to run
Test / test (push) Waiting to run
Test / test-community (push) Waiting to run
Lint / golangci-lint (push) Waiting to run
Lint / semgrep (push) Waiting to run
Release / Release (push) Waiting to run
Scan for secrets / test (push) Waiting to run
Test / test (push) Waiting to run
Test / test-community (push) Waiting to run
* feat(detector/copper): improve verification with stricter email matching * revert tags in int test --------- Co-authored-by: Kashif Khan <[email protected]>
This commit is contained in:
co-authored by
Kashif Khan
parent
1d87fba935
commit
7583da5f26
@@ -2,6 +2,9 @@ package copper
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -27,6 +30,11 @@ var (
|
|||||||
idPat = regexp.MustCompile(`\b([a-z0-9]{4,25}@[a-zA-Z0-9]{2,12}.[a-zA-Z0-9]{2,6})\b`)
|
idPat = regexp.MustCompile(`\b([a-z0-9]{4,25}@[a-zA-Z0-9]{2,12}.[a-zA-Z0-9]{2,6})\b`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UserApiResponse struct {
|
||||||
|
Id int `json:"id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
// Keywords are used for efficiently pre-filtering chunks.
|
// Keywords are used for efficiently pre-filtering chunks.
|
||||||
// Use identifiers in the secret preferably, or the provider name.
|
// Use identifiers in the secret preferably, or the provider name.
|
||||||
func (s Scanner) Keywords() []string {
|
func (s Scanner) Keywords() []string {
|
||||||
@@ -52,26 +60,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
}
|
}
|
||||||
|
|
||||||
if verify {
|
if verify {
|
||||||
|
isVerified, verificationErr := verifyCopper(ctx, client, resIdMatch, resMatch)
|
||||||
payload := strings.NewReader(`{
|
s1.Verified = isVerified
|
||||||
"page_size": 25,
|
s1.SetVerificationError(verificationErr, resMatch)
|
||||||
"sort_by": "name"
|
|
||||||
}`)
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.copper.com/developer_api/v1/tasks/search", payload)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
req.Header.Add("X-PW-AccessToken", resMatch)
|
|
||||||
req.Header.Add("X-PW-Application", "developer_api")
|
|
||||||
req.Header.Add("X-PW-UserEmail", resIdMatch)
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
res, err := client.Do(req)
|
|
||||||
if err == nil {
|
|
||||||
defer res.Body.Close()
|
|
||||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
|
||||||
s1.Verified = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, s1)
|
results = append(results, s1)
|
||||||
@@ -83,6 +74,54 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
|||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func verifyCopper(ctx context.Context, client *http.Client, email, apiKey string) (bool, error) {
|
||||||
|
req, err := http.NewRequestWithContext(
|
||||||
|
ctx,
|
||||||
|
http.MethodGet,
|
||||||
|
"https://api.copper.com/developer_api/v1/users/me",
|
||||||
|
http.NoBody,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
req.Header.Add("X-PW-AccessToken", apiKey)
|
||||||
|
req.Header.Add("X-PW-Application", "developer_api")
|
||||||
|
req.Header.Add("X-PW-UserEmail", email)
|
||||||
|
req.Header.Add("Content-Type", "application/json")
|
||||||
|
res, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_, _ = io.Copy(io.Discard, res.Body)
|
||||||
|
_ = res.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
switch res.StatusCode {
|
||||||
|
case http.StatusOK:
|
||||||
|
respBytes, err := io.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var respBody UserApiResponse
|
||||||
|
if err := json.Unmarshal(respBytes, &respBody); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// strict verification with email in credentials
|
||||||
|
if respBody.Email == email {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, fmt.Errorf("email mismatch in verification response")
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
return false, nil
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("unexpected status code :%d", res.StatusCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s Scanner) Type() detectorspb.DetectorType {
|
func (s Scanner) Type() detectorspb.DetectorType {
|
||||||
return detectorspb.DetectorType_Copper
|
return detectorspb.DetectorType_Copper
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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/common"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
|
||||||
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
|
||||||
@@ -96,9 +96,11 @@ func TestCopper_FromChunk(t *testing.T) {
|
|||||||
t.Fatalf("no raw secret present: \n %+v", got[i])
|
t.Fatalf("no raw secret present: \n %+v", got[i])
|
||||||
}
|
}
|
||||||
got[i].Raw = nil
|
got[i].Raw = nil
|
||||||
|
got[i].RawV2 = nil
|
||||||
}
|
}
|
||||||
if diff := pretty.Compare(got, tt.want); diff != "" {
|
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "primarySecret")
|
||||||
t.Errorf("Copper.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
||||||
|
t.Errorf("Abstract.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user