diff --git a/pkg/detectors/anthropic/anthropic.go b/pkg/detectors/anthropic/anthropic.go index 7bbf24418..47f07931a 100644 --- a/pkg/detectors/anthropic/anthropic.go +++ b/pkg/detectors/anthropic/anthropic.go @@ -2,6 +2,7 @@ package anthropic import ( "context" + "errors" "fmt" "net/http" "strings" @@ -23,27 +24,32 @@ 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`) + keyPat = regexp.MustCompile(`\b(sk-ant-(?:admin01|api03)-[\w\-]{93}AA)\b`) + + // verification endpoints + apiKeyEndpoint = "https://api.anthropic.com/v1/models" + adminKeyEndpoint = "https://api.anthropic.com/v1/organizations/api_keys" ) // 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"} + return []string{"sk-ant-api03", "sk-ant-admin01"} } // 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) + keys := keyPat.FindAllStringSubmatch(dataStr, -1) - for _, match := range matches { - resMatch := strings.TrimSpace(match[1]) + for _, key := range keys { + keyMatch := strings.TrimSpace(key[1]) s1 := detectors.Result{ DetectorType: detectorspb.DetectorType_Anthropic, - Raw: []byte(resMatch), + Raw: []byte(keyMatch), + ExtraData: make(map[string]string), } if verify { @@ -51,12 +57,27 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result if client == nil { client = defaultClient } - isVerified, err := verifyToken(ctx, client, resMatch) + + isAdminKey := isAdminKey(keyMatch) + var isVerified bool + var err error + + if isAdminKey { + isVerified, err = verifyAnthropicKey(ctx, client, adminKeyEndpoint, keyMatch) + s1.ExtraData["Type"] = "Admin Key" + } else if !isAdminKey { + isVerified, err = verifyAnthropicKey(ctx, client, apiKeyEndpoint, keyMatch) + s1.ExtraData["Type"] = "API Key" + } else { + return nil, errors.New("unknown key type detected for anthropic") + } + s1.Verified = isVerified - s1.SetVerificationError(err, resMatch) + s1.SetVerificationError(err, keyMatch) + if s1.Verified { s1.AnalysisInfo = map[string]string{ - "key": resMatch, + "key": keyMatch, } } } @@ -67,14 +88,22 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } -func verifyToken(ctx context.Context, client *http.Client, apiKey string) (bool, error) { - // https://docs.anthropic.com/en/api/models-list - req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.anthropic.com/v1/models", http.NoBody) +/* +verifyAnthropicKey verify the anthropic key passed against the endpoint + +Endpoints: + + - For api keys: https://docs.anthropic.com/en/api/models-list + + - For admin keys: https://docs.anthropic.com/en/api/admin-api/apikeys/list-api-keys +*/ +func verifyAnthropicKey(ctx context.Context, client *http.Client, endpoint, key string) (bool, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody) if err != nil { return false, nil } - req.Header.Set("x-api-key", apiKey) + req.Header.Set("x-api-key", key) req.Header.Set("Content-Type", "application/json") req.Header.Set("anthropic-version", "2023-06-01") @@ -104,3 +133,7 @@ func (s Scanner) Type() detectorspb.DetectorType { func (s Scanner) Description() string { return "Anthropic is an AI research company. The API keys can be used to access their AI models and services." } + +func isAdminKey(key string) bool { + return strings.HasPrefix(key, "sk-ant-admin01") +} diff --git a/pkg/detectors/anthropic/anthropic_integration_test.go b/pkg/detectors/anthropic/anthropic_integration_test.go index d36c4f533..fd7648aea 100644 --- a/pkg/detectors/anthropic/anthropic_integration_test.go +++ b/pkg/detectors/anthropic/anthropic_integration_test.go @@ -25,7 +25,8 @@ func TestAnthropic_FromChunk(t *testing.T) { if err != nil { t.Fatalf("could not get test secrets from GCP: %s", err) } - secret := testSecrets.MustGetField("ANTHROPIC") + + apiKey := testSecrets.MustGetField("ANTHROPIC") inactiveSecret := testSecrets.MustGetField("ANTHROPIC_INACTIVE") type args struct { @@ -46,7 +47,7 @@ func TestAnthropic_FromChunk(t *testing.T) { s: Scanner{}, args: args{ ctx: context.Background(), - data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", secret)), + data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", apiKey)), verify: true, }, want: []detectors.Result{ @@ -92,7 +93,7 @@ func TestAnthropic_FromChunk(t *testing.T) { 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)), + data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", apiKey)), verify: true, }, want: []detectors.Result{ diff --git a/pkg/detectors/anthropic/anthropic_test.go b/pkg/detectors/anthropic/anthropic_test.go index e05018241..dd3515737 100644 --- a/pkg/detectors/anthropic/anthropic_test.go +++ b/pkg/detectors/anthropic/anthropic_test.go @@ -17,9 +17,10 @@ var ( Server: api-secure-03.internal Service: Anthropic API Gateway API Key: sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA + Admin Key: sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA 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. + A new API and Admin 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" ) @@ -36,7 +37,10 @@ func TestAnthropic_Pattern(t *testing.T) { { name: "valid pattern", input: validPattern, - want: []string{"sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA"}, + want: []string{ + "sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA", + "sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA", + }, }, { name: "invalid pattern",