Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 64 additions & 19 deletions pkg/detectors/mesibo/mesibo.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
package mesibo

import (
"bytes"
"context"
regexp "github.com/wasilibs/go-re2"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"

regexp "github.com/wasilibs/go-re2"

"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detector_typepb"
)

type Scanner struct{}
type Scanner struct {
client *http.Client
}

type apiResponse struct {
Code int `json:"code"`
}

// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)

var (
client = 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{"mesibo"}) + `\b([0-9A-Za-z]{64})\b`)
)

func (s Scanner) getClient() *http.Client {
if s.client != nil {
return s.client
}
return common.SaneHttpClient()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing defaultClient causes new HTTP client per call

Low Severity

The getClient() method calls common.SaneHttpClient() on every invocation when s.client is nil, creating a new HTTP client and transport each time. Every other detector in the codebase (e.g., abstract, abuseipdb, adafruitio) initializes a package-level defaultClient variable once via var defaultClient = common.SaneHttpClient() and returns that from getClient(). This deviation prevents HTTP connection reuse across verification calls.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5cdc6d4. Configure here.


// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
Expand All @@ -45,22 +60,9 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
}

if verify {
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.mesibo.com/api.php?op=useradd&token="+resMatch, nil)
s1.Verified, err = s.verify(ctx, resMatch)
if err != nil {
continue
}
res, err := client.Do(req)
if err == nil {
defer res.Body.Close()
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
continue
}
body := string(bodyBytes)

if !strings.Contains(body, "AUTHFAIL") {
s1.Verified = true
}
s1.SetVerificationError(err, resMatch)
}
}

Expand All @@ -70,6 +72,49 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
return results, nil
}

// verify checks the validity of a Mesibo app token against the backend API.
// https://docs.mesibo.com/api/backend-api/
func (s Scanner) verify(ctx context.Context, token string) (bool, error) {

// We use the `useradd` operation as a probe: a valid token will yield
// code 400 (bad request due to missing required user parameters, but
// authentication succeeded), whereas an invalid or unauthorized
// token will yield a different code such as 403.
payload, err := json.Marshal(map[string]string{"op": "useradd", "token": token})
if err != nil {
return false, fmt.Errorf("failed to marshal request payload: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.mesibo.com/backend", bytes.NewBuffer(payload))
if err != nil {
return false, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
res, err := s.getClient().Do(req)
if err != nil {
return false, fmt.Errorf("failed to execute request: %w", err)
}
defer res.Body.Close()

// The backend API always returns HTTP 200, with the actual result encoded in the
// JSON response body.
if res.StatusCode != http.StatusOK {
return false, fmt.Errorf("unexpected status code: %d", res.StatusCode)
}
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
return false, fmt.Errorf("failed to read response body: %w", err)
}
var result apiResponse
if err := json.Unmarshal(bodyBytes, &result); err != nil {
return false, fmt.Errorf("failed to unmarshal response body: %w", err)
}
// The `code` field contains an RFC 9110 compliant HTTP status
// code indicating the outcome of the operation.
// code 400 means valid token (bad request due to missing params, but auth passed)
// any other code means invalid/unauthorized
return result.Code == http.StatusBadRequest, nil
}

func (s Scanner) Type() detector_typepb.DetectorType {
return detector_typepb.DetectorType_Mesibo
}
Expand Down
41 changes: 31 additions & 10 deletions pkg/detectors/mesibo/mesibo_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -32,11 +33,12 @@ func TestMesibo_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",
Expand Down Expand Up @@ -81,11 +83,27 @@ func TestMesibo_FromChunk(t *testing.T) {
want: nil,
wantErr: false,
},
{
name: "found, verification error on 502",
s: Scanner{client: common.ConstantResponseHttpClient(502, "")},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a mesibo secret %s within", secret)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detector_typepb.DetectorType_Mesibo,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
}
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("Mesibo.FromData() error = %v, wantErr %v", err, tt.wantErr)
return
Expand All @@ -95,9 +113,12 @@ func TestMesibo_FromChunk(t *testing.T) {
t.Fatalf("no raw secret present: \n %+v", got[i])
}
got[i].Raw = nil
if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationErr=%v, got verificationError=%v", tt.wantVerificationErr, got[i].VerificationError())
}
}
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("Mesibo.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
if diff := cmp.Diff(tt.want, got, cmpopts.IgnoreUnexported(detectors.Result{})); diff != "" {
t.Errorf("Mesibo.FromData() %s diff: (-want +got)\n%s", tt.name, diff)
}
})
}
Expand Down
Loading