anthropic api key analyzer (#3878)
* initial commit * added anthropic api key analyzer * added secret info type * resolved comments
This commit is contained in:
@@ -61,6 +61,7 @@ const (
|
||||
const (
|
||||
AnalyzerTypeInvalid AnalyzerType = iota
|
||||
AnalyzerTypeAirbrake
|
||||
AnalyzerAnthropic
|
||||
AnalyzerTypeAsana
|
||||
AnalyzerTypeBitbucket
|
||||
AnalyzerTypeDockerHub
|
||||
@@ -90,6 +91,7 @@ const (
|
||||
var analyzerTypeStrings = map[AnalyzerType]string{
|
||||
AnalyzerTypeInvalid: "Invalid",
|
||||
AnalyzerTypeAirbrake: "Airbrake",
|
||||
AnalyzerAnthropic: "Anthropic",
|
||||
AnalyzerTypeAsana: "Asana",
|
||||
AnalyzerTypeBitbucket: "Bitbucket",
|
||||
AnalyzerTypeDockerHub: "DockerHub",
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package anthropic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jedib0t/go-pretty/table"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
const (
|
||||
// Key Types
|
||||
APIKey = "API-Key"
|
||||
)
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
// SecretInfo hold the information about the anthropic key
|
||||
type SecretInfo struct {
|
||||
Valid bool
|
||||
Type string // key type - TODO: Handle Anthropic Admin Keys
|
||||
Reference string
|
||||
AnthropicResources []AnthropicResource
|
||||
Permissions string // always full_access
|
||||
Misc map[string]string
|
||||
}
|
||||
|
||||
// AnthropicResource is any resource that can be accessed with anthropic key
|
||||
type AnthropicResource struct {
|
||||
ID string
|
||||
Name string
|
||||
Type string
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
func (a Analyzer) Type() analyzers.AnalyzerType {
|
||||
return analyzers.AnalyzerAnthropic
|
||||
}
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
key, exist := credInfo["key"]
|
||||
if !exist {
|
||||
return nil, errors.New("key not found in credentials info")
|
||||
}
|
||||
|
||||
secretInfo, err := AnalyzePermissions(a.Cfg, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secretInfoToAnalyzerResult(secretInfo), nil
|
||||
}
|
||||
|
||||
func AnalyzeAndPrintPermissions(cfg *config.Config, key string) {
|
||||
info, err := AnalyzePermissions(cfg, key)
|
||||
if err != nil {
|
||||
// just print the error in cli and continue as a partial success
|
||||
color.Red("[x] Error : %s", err.Error())
|
||||
}
|
||||
|
||||
if info == nil {
|
||||
color.Red("[x] Error : %s", "No information found")
|
||||
return
|
||||
}
|
||||
|
||||
if info.Valid {
|
||||
color.Green("[!] Valid Anthropic API key\n\n")
|
||||
// no user information
|
||||
// print full access permission
|
||||
printPermission(info.Permissions)
|
||||
// print resources
|
||||
printAnthropicResources(info.AnthropicResources)
|
||||
|
||||
color.Yellow("\n[i] Expires: Never")
|
||||
}
|
||||
}
|
||||
|
||||
func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
|
||||
// create a HTTP client
|
||||
client := analyzers.NewAnalyzeClient(cfg)
|
||||
|
||||
var secretInfo = &SecretInfo{
|
||||
Type: APIKey, // TODO: implement Admin-Key type as well
|
||||
}
|
||||
|
||||
if err := listModels(client, key, secretInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := listMessageBatches(client, key, secretInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// anthropic key has full access only
|
||||
secretInfo.Permissions = PermissionStrings[FullAccess]
|
||||
secretInfo.Valid = true
|
||||
|
||||
return secretInfo, nil
|
||||
}
|
||||
|
||||
// secretInfoToAnalyzerResult translate secret info to Analyzer Result
|
||||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzers.AnalyzerAnthropic,
|
||||
Metadata: map[string]any{"Valid_Key": info.Valid},
|
||||
Bindings: make([]analyzers.Binding, len(info.AnthropicResources)),
|
||||
}
|
||||
|
||||
// extract information to create bindings and append to result bindings
|
||||
for _, Anthropicresource := range info.AnthropicResources {
|
||||
binding := analyzers.Binding{
|
||||
Resource: analyzers.Resource{
|
||||
Name: Anthropicresource.Name,
|
||||
FullyQualifiedName: Anthropicresource.ID,
|
||||
Type: Anthropicresource.Type,
|
||||
Metadata: map[string]any{},
|
||||
},
|
||||
Permission: analyzers.Permission{
|
||||
Value: info.Permissions,
|
||||
},
|
||||
}
|
||||
|
||||
for key, value := range Anthropicresource.Metadata {
|
||||
binding.Resource.Metadata[key] = value
|
||||
}
|
||||
|
||||
result.Bindings = append(result.Bindings, binding)
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func printPermission(permission string) {
|
||||
color.Yellow("[i] Permissions:")
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{"Permission"})
|
||||
t.AppendRow(table.Row{color.GreenString(permission)})
|
||||
t.Render()
|
||||
}
|
||||
|
||||
func printAnthropicResources(resources []AnthropicResource) {
|
||||
color.Green("\n[i] Resources:")
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{"Resource Type", "Resource ID", "Resource Name"})
|
||||
for _, resource := range resources {
|
||||
t.AppendRow(table.Row{color.GreenString(resource.Type), color.GreenString(resource.ID), color.GreenString(resource.Name)})
|
||||
}
|
||||
t.Render()
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package anthropic
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed result_output.json
|
||||
var expectedOutput []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*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")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
secret string
|
||||
want []byte // JSON string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid anthropic key",
|
||||
secret: secret,
|
||||
want: expectedOutput,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{"key": tt.secret})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Parse the expected JSON string
|
||||
var wantObj analyzers.AnalyzerResult
|
||||
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
|
||||
t.Fatalf("could not unmarshal want JSON string: %s", err)
|
||||
}
|
||||
|
||||
// Marshal the expected result to JSON (to normalize)
|
||||
wantJSON, err := json.Marshal(wantObj)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Compare the JSON strings
|
||||
if string(gotJSON) != string(wantJSON) {
|
||||
// Pretty-print both JSON strings for easier comparison
|
||||
var gotIndented, wantIndented []byte
|
||||
gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package anthropic
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
FullAccess Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
FullAccess: "full_access",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"full_access": FullAccess,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
FullAccess: 1,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: FullAccess,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
permissions:
|
||||
- full_access
|
||||
@@ -0,0 +1,121 @@
|
||||
package anthropic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ModelsResponse struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Type string `json:"type"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type MessageResponse struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
ProcessingStatus string `json:"processing_status"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
ResultsURL string `json:"results_url"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// makeAnthropicRequest send the API request to passed url with passed key as API Key and return response body and status code
|
||||
func makeAnthropicRequest(client *http.Client, url, key string) ([]byte, int, error) {
|
||||
// create request
|
||||
req, err := http.NewRequest(http.MethodGet, url, http.NoBody)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// add required keys in the header
|
||||
req.Header.Set("x-api-key", key)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("anthropic-version", "2023-06-01")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
responseBodyByte, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return responseBodyByte, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func listModels(client *http.Client, key string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, "https://api.anthropic.com/v1/models", key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var models ModelsResponse
|
||||
|
||||
if err := json.Unmarshal(response, &models); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, model := range models.Data {
|
||||
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
|
||||
ID: model.ID,
|
||||
Name: model.DisplayName,
|
||||
Type: model.Type,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
case http.StatusNotFound, http.StatusUnauthorized:
|
||||
return fmt.Errorf("invalid/revoked api-key")
|
||||
default:
|
||||
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func listMessageBatches(client *http.Client, key string, secretInfo *SecretInfo) error {
|
||||
response, statusCode, err := makeAnthropicRequest(client, "https://api.anthropic.com/v1/messages/batches", key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch statusCode {
|
||||
case http.StatusOK:
|
||||
var messageBatches MessageResponse
|
||||
|
||||
if err := json.Unmarshal(response, &messageBatches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, messageBatch := range messageBatches.Data {
|
||||
secretInfo.AnthropicResources = append(secretInfo.AnthropicResources, AnthropicResource{
|
||||
ID: messageBatch.ID,
|
||||
Name: "", // no name
|
||||
Type: messageBatch.Type,
|
||||
Metadata: map[string]string{
|
||||
"expires_at": messageBatch.ExpiresAt,
|
||||
"results_url": messageBatch.ResultsURL,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
case http.StatusNotFound, http.StatusUnauthorized:
|
||||
return fmt.Errorf("invalid/revoked api-key")
|
||||
default:
|
||||
return fmt.Errorf("unexpected status code: %d while fetching models", statusCode)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
{
|
||||
"AnalyzerType": 2,
|
||||
"Bindings": [
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Claude 3.5 Sonnet (New)",
|
||||
"FullyQualifiedName": "claude-3-5-sonnet-20241022",
|
||||
"Type": "model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "full_access",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Claude 3.5 Haiku",
|
||||
"FullyQualifiedName": "claude-3-5-haiku-20241022",
|
||||
"Type": "model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "full_access",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Claude 3.5 Sonnet (Old)",
|
||||
"FullyQualifiedName": "claude-3-5-sonnet-20240620",
|
||||
"Type": "model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "full_access",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Claude 3 Haiku",
|
||||
"FullyQualifiedName": "claude-3-haiku-20240307",
|
||||
"Type": "model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "full_access",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "Claude 3 Opus",
|
||||
"FullyQualifiedName": "claude-3-opus-20240229",
|
||||
"Type": "model",
|
||||
"Metadata": {},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "full_access",
|
||||
"Parent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"Resource": {
|
||||
"Name": "",
|
||||
"FullyQualifiedName": "msgbatch_015FDqbx29LDeVvbwwyCe314",
|
||||
"Type": "message_batch",
|
||||
"Metadata": {
|
||||
"expires_at": "2025-02-05T07:36:34.761695+00:00",
|
||||
"results_url": "https://api.anthropic.com/v1/messages/batches/msgbatch_015FDqbx29LDeVvbwwyCe314/results"
|
||||
},
|
||||
"Parent": null
|
||||
},
|
||||
"Permission": {
|
||||
"Value": "full_access",
|
||||
"Parent": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"UnboundedResources": null,
|
||||
"Metadata": {
|
||||
"Valid_Key": true
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/airbrake"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/anthropic"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dockerhub"
|
||||
@@ -91,5 +92,7 @@ func Run(keyType string, secretInfo SecretInfo) {
|
||||
notion.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "dockerhub":
|
||||
dockerhub.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["username"], secretInfo.Parts["pat"])
|
||||
case "anthropic":
|
||||
anthropic.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package anthropic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@@ -56,6 +54,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
isVerified, err := verifyToken(ctx, client, resMatch)
|
||||
s1.Verified = isVerified
|
||||
s1.SetVerificationError(err, resMatch)
|
||||
if s1.Verified {
|
||||
s1.AnalysisInfo = map[string]string{
|
||||
"key": resMatch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, s1)
|
||||
@@ -64,31 +67,13 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
func verifyToken(ctx context.Context, client *http.Client, apiKey string) (bool, error) {
|
||||
body := map[string]any{
|
||||
"model": "claude-3-opus-20240229",
|
||||
"max_tokens": 1024,
|
||||
"messages": []map[string]string{
|
||||
{"role": "user", "content": "Hello, world"},
|
||||
},
|
||||
}
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
// https://docs.anthropic.com/en/api/models-list
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.anthropic.com/v1/models", http.NoBody)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// https://docs.anthropic.com/claude/reference/messages_post
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.anthropic.com/v1/messages", bytes.NewReader(bodyBytes))
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
req.Header.Set("x-api-key", apiKey)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("anthropic-version", "2023-06-01")
|
||||
@@ -103,14 +88,8 @@ func verifyToken(ctx context.Context, client *http.Client, apiKey string) (bool,
|
||||
case http.StatusOK:
|
||||
return true, nil
|
||||
|
||||
case http.StatusBadRequest:
|
||||
var resp response
|
||||
if err = json.NewDecoder(res.Body).Decode(&resp); err != nil {
|
||||
return false, fmt.Errorf("unexpected HTTP response body: %w", err)
|
||||
}
|
||||
return true, nil
|
||||
|
||||
case http.StatusUnauthorized:
|
||||
case http.StatusNotFound, http.StatusUnauthorized:
|
||||
// 404 is returned if api key is disabled or not found
|
||||
return false, nil
|
||||
|
||||
default:
|
||||
|
||||
@@ -104,23 +104,6 @@ func TestAnthropic_FromChunk(t *testing.T) {
|
||||
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) {
|
||||
@@ -137,7 +120,7 @@ func TestAnthropic_FromChunk(t *testing.T) {
|
||||
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
|
||||
}
|
||||
}
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError", "AnalysisInfo")
|
||||
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
||||
t.Errorf("Anthropic.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,16 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
validPattern = "sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA"
|
||||
validPattern = `
|
||||
System Log - Authentication Token Issued
|
||||
Date: 2025-02-04 14:32:10 UTC
|
||||
Server: api-secure-03.internal
|
||||
Service: Anthropic API Gateway
|
||||
API Key: sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA
|
||||
|
||||
Log Entry:
|
||||
A new API key has been generated for service authentication. Please ensure that this key remains confidential and is not exposed in any public repositories or logs.
|
||||
`
|
||||
invalidPattern = "sk-ant-api03-abc123xyz-456de-klMnopqrstuvwx-3456yza789bcde-1234fghijklmnopAA"
|
||||
)
|
||||
|
||||
@@ -27,7 +36,7 @@ func TestAnthropic_Pattern(t *testing.T) {
|
||||
{
|
||||
name: "valid pattern",
|
||||
input: validPattern,
|
||||
want: []string{validPattern},
|
||||
want: []string{"sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA"},
|
||||
},
|
||||
{
|
||||
name: "invalid pattern",
|
||||
|
||||
Reference in New Issue
Block a user