Skip to content

Commit 3df92ae

Browse files
Updated anthropic detector to detect admin keys
1 parent 21fbe08 commit 3df92ae

File tree

3 files changed

+56
-18
lines changed

3 files changed

+56
-18
lines changed

pkg/detectors/anthropic/anthropic.go

+46-13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package anthropic
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"net/http"
78
"strings"
@@ -23,40 +24,60 @@ var _ detectors.Detector = (*Scanner)(nil)
2324
var (
2425
defaultClient = common.SaneHttpClient()
2526
// Make sure that your group is surrounded in boundary characters such as below to reduce false positives.
26-
keyPat = regexp.MustCompile(`\b(sk-ant-api03-[\w\-]{93}AA)\b`)
27+
keyPat = regexp.MustCompile(`\b(sk-ant-(?:admin01|api03)-[\w\-]{93}AA)\b`)
28+
29+
// verification endpoints
30+
apiKeyEndpoint = "https://api.anthropic.com/v1/models"
31+
adminKeyEndpoint = "https://api.anthropic.com/v1/organizations/api_keys"
2732
)
2833

2934
// Keywords are used for efficiently pre-filtering chunks.
3035
// Use identifiers in the secret preferably, or the provider name.
3136
func (s Scanner) Keywords() []string {
32-
return []string{"sk-ant-api03"}
37+
return []string{"sk-ant-api03", "sk-ant-admin01"}
3338
}
3439

3540
// FromData will find and optionally verify Anthropic secrets in a given set of bytes.
3641
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
3742
dataStr := string(data)
3843

39-
matches := keyPat.FindAllStringSubmatch(dataStr, -1)
44+
keys := keyPat.FindAllStringSubmatch(dataStr, -1)
4045

41-
for _, match := range matches {
42-
resMatch := strings.TrimSpace(match[1])
46+
for _, key := range keys {
47+
keyMatch := strings.TrimSpace(key[1])
4348

4449
s1 := detectors.Result{
4550
DetectorType: detectorspb.DetectorType_Anthropic,
46-
Raw: []byte(resMatch),
51+
Raw: []byte(keyMatch),
52+
ExtraData: make(map[string]string),
4753
}
4854

4955
if verify {
5056
client := s.client
5157
if client == nil {
5258
client = defaultClient
5359
}
54-
isVerified, err := verifyToken(ctx, client, resMatch)
60+
61+
isAdminKey := isAdminKey(keyMatch)
62+
var isVerified bool
63+
var err error
64+
65+
if isAdminKey {
66+
isVerified, err = verifyAnthropicKey(ctx, client, adminKeyEndpoint, keyMatch)
67+
s1.ExtraData["Type"] = "Admin Key"
68+
} else if !isAdminKey {
69+
isVerified, err = verifyAnthropicKey(ctx, client, apiKeyEndpoint, keyMatch)
70+
s1.ExtraData["Type"] = "API Key"
71+
} else {
72+
return nil, errors.New("unknown key type detected for anthropic")
73+
}
74+
5575
s1.Verified = isVerified
56-
s1.SetVerificationError(err, resMatch)
76+
s1.SetVerificationError(err, keyMatch)
77+
5778
if s1.Verified {
5879
s1.AnalysisInfo = map[string]string{
59-
"key": resMatch,
80+
"key": keyMatch,
6081
}
6182
}
6283
}
@@ -67,14 +88,22 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
6788
return results, nil
6889
}
6990

70-
func verifyToken(ctx context.Context, client *http.Client, apiKey string) (bool, error) {
71-
// https://docs.anthropic.com/en/api/models-list
72-
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.anthropic.com/v1/models", http.NoBody)
91+
/*
92+
verifyAnthropicKey verify the anthropic key passed against the endpoint
93+
94+
Endpoints:
95+
96+
- For api keys: https://docs.anthropic.com/en/api/models-list
97+
98+
- For admin keys: https://docs.anthropic.com/en/api/admin-api/apikeys/list-api-keys
99+
*/
100+
func verifyAnthropicKey(ctx context.Context, client *http.Client, endpoint, key string) (bool, error) {
101+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, http.NoBody)
73102
if err != nil {
74103
return false, nil
75104
}
76105

77-
req.Header.Set("x-api-key", apiKey)
106+
req.Header.Set("x-api-key", key)
78107
req.Header.Set("Content-Type", "application/json")
79108
req.Header.Set("anthropic-version", "2023-06-01")
80109

@@ -104,3 +133,7 @@ func (s Scanner) Type() detectorspb.DetectorType {
104133
func (s Scanner) Description() string {
105134
return "Anthropic is an AI research company. The API keys can be used to access their AI models and services."
106135
}
136+
137+
func isAdminKey(key string) bool {
138+
return strings.HasPrefix(key, "sk-ant-admin01")
139+
}

pkg/detectors/anthropic/anthropic_integration_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ func TestAnthropic_FromChunk(t *testing.T) {
2525
if err != nil {
2626
t.Fatalf("could not get test secrets from GCP: %s", err)
2727
}
28-
secret := testSecrets.MustGetField("ANTHROPIC")
28+
29+
apiKey := testSecrets.MustGetField("ANTHROPIC")
2930
inactiveSecret := testSecrets.MustGetField("ANTHROPIC_INACTIVE")
3031

3132
type args struct {
@@ -46,7 +47,7 @@ func TestAnthropic_FromChunk(t *testing.T) {
4647
s: Scanner{},
4748
args: args{
4849
ctx: context.Background(),
49-
data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", secret)),
50+
data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", apiKey)),
5051
verify: true,
5152
},
5253
want: []detectors.Result{
@@ -92,7 +93,7 @@ func TestAnthropic_FromChunk(t *testing.T) {
9293
s: Scanner{client: common.SaneHttpClientTimeOut(1 * time.Microsecond)},
9394
args: args{
9495
ctx: context.Background(),
95-
data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", secret)),
96+
data: []byte(fmt.Sprintf("You can find a anthropic secret %s within", apiKey)),
9697
verify: true,
9798
},
9899
want: []detectors.Result{

pkg/detectors/anthropic/anthropic_test.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ var (
1717
Server: api-secure-03.internal
1818
Service: Anthropic API Gateway
1919
API Key: sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA
20+
Admin Key: sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA
2021
2122
Log Entry:
22-
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.
23+
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.
2324
`
2425
invalidPattern = "sk-ant-api03-abc123xyz-456de-klMnopqrstuvwx-3456yza789bcde-1234fghijklmnopAA"
2526
)
@@ -36,7 +37,10 @@ func TestAnthropic_Pattern(t *testing.T) {
3637
{
3738
name: "valid pattern",
3839
input: validPattern,
39-
want: []string{"sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA"},
40+
want: []string{
41+
"sk-ant-api03-abc123xyz-456def789ghij-klmnopqrstuvwx-3456yza789bcde-1234fghijklmnopby56aaaogaopaaaabc123xyzAA",
42+
"sk-ant-admin01-abc12fake-456def789ghij-klmnopqrstuvwx-3456yza789bcde-12fakehijklmnopby56aaaogaopaaaabc123xyzAA",
43+
},
4044
},
4145
{
4246
name: "invalid pattern",

0 commit comments

Comments
 (0)