elevenlabs analyzer (#3850)
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

* added analyzer initial working structure

* restructured the resources

* added history read api

* added history delete api

* fixed http method issue

* added delete dubbing api and refactored the code

* added dubbing read api

* optimized requests and added projects api

* added pronunciation dictionaries apis

* fixed linter

* added models,audionative,workspace apis

* added some last apis

* Deleted \

* fixed linter

* few more enhancements and test cases added

* added go generate command

* optimized the code and resolved the comments

* fixed linter

* fixed indentation

* resolved comments

* kicked imposter print statement

* formatted cli.go

* Update pkg/analyzer/analyzers/elevenlabs/elevenlabs.go

Co-authored-by: Eng Zer Jun <[email protected]>

* resolved comments

* added secret info methods which safely append and read permission and resources

---------

Co-authored-by: Eng Zer Jun <[email protected]>
This commit is contained in:
Kashif Khan
2025-02-25 07:59:56 -08:00
committed by GitHub
co-authored by Eng Zer Jun
parent 03e8af1075
commit f7ff9980e3
11 changed files with 1986 additions and 3 deletions
+3 -1
View File
@@ -65,6 +65,7 @@ const (
AnalyzerTypeAsana
AnalyzerTypeBitbucket
AnalyzerTypeDockerHub
AnalyzerTypeElevenLabs
AnalyzerTypeGitHub
AnalyzerTypeGitLab
AnalyzerTypeHuggingFace
@@ -97,7 +98,9 @@ var analyzerTypeStrings = map[AnalyzerType]string{
AnalyzerAnthropic: "Anthropic",
AnalyzerTypeAsana: "Asana",
AnalyzerTypeBitbucket: "Bitbucket",
AnalyzerTypeDigitalOcean: "DigitalOcean",
AnalyzerTypeDockerHub: "DockerHub",
AnalyzerTypeElevenLabs: "ElevenLabs",
AnalyzerTypeGitHub: "GitHub",
AnalyzerTypeGitLab: "GitLab",
AnalyzerTypeHuggingFace: "HuggingFace",
@@ -117,7 +120,6 @@ var analyzerTypeStrings = map[AnalyzerType]string{
AnalyzerTypeTwilio: "Twilio",
AnalyzerTypePrivateKey: "PrivateKey",
AnalyzerTypeNotion: "Notion",
AnalyzerTypeDigitalOcean: "DigitalOcean",
// Add new mappings here
}
@@ -0,0 +1,468 @@
package elevenlabs
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"slices"
"strings"
"sync"
"github.com/fatih/color"
"github.com/google/uuid"
"github.com/jedib0t/go-pretty/v6/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)
type Analyzer struct {
Cfg *config.Config
}
// SecretInfo hold information about key
type SecretInfo struct {
User User // the owner of key
Valid bool
Reference string
Permissions []string // list of Permissions assigned to the key
ElevenLabsResources []ElevenLabsResource // list of resources the key has access to
mu sync.RWMutex
}
// AppendPermissions safely append new permission to secret info permissions list.
func (s *SecretInfo) AppendPermission(perm string) {
s.mu.Lock()
defer s.mu.Unlock()
s.Permissions = append(s.Permissions, perm)
}
// HasPermission safely read secret info permission list to check if passed permission exist in the list.
func (s *SecretInfo) HasPermission(perm Permission) bool {
s.mu.Lock()
defer s.mu.Unlock()
permissionString, _ := perm.ToString()
return slices.Contains(s.Permissions, permissionString)
}
// AppendResource safely append new resource to secret info elevenlabs resource list.
func (s *SecretInfo) AppendResource(resource ElevenLabsResource) {
s.mu.Lock()
defer s.mu.Unlock()
s.ElevenLabsResources = append(s.ElevenLabsResources, resource)
}
// User hold the information about user to whom the key belongs to
type User struct {
ID string
Name string
SubscriptionTier string
SubscriptionStatus string
}
// ElevenLabsResource hold information about the elevenlabs resource the key has access
type ElevenLabsResource struct {
ID string
Name string
Type string
Metadata map[string]string
Permission string
}
func (a Analyzer) Type() analyzers.AnalyzerType {
return analyzers.AnalyzerTypeElevenLabs
}
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
// check if the `key` exist in the credentials info
key, exist := credInfo["key"]
if !exist {
return nil, errors.New("key not found in credentials info")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return secretInfoToAnalyzerResult(info), nil
}
// AnalyzePermissions check if key is valid and analyzes the permission for the key
func AnalyzePermissions(cfg *config.Config, key string) (*SecretInfo, error) {
// create http client
client := analyzers.NewAnalyzeClient(cfg)
var secretInfo = &SecretInfo{}
// fetch user information using the key
user, err := fetchUser(client, key)
if err != nil {
return nil, err
}
secretInfo.Valid = true
// if user is not nil, that means the key has user read permission. Set the user information in secret info user
// user can only be nil when the key is valid but it does not have a user read permission
if user != nil {
elevenLabsUserToSecretInfoUser(*user, secretInfo)
}
// get elevenlabs resources with permissions
if err := getElevenLabsResources(client, key, secretInfo); err != nil {
return nil, err
}
return 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 ElevenLabs API key\n\n")
// print user information
printUser(info.User)
// print permissions
printPermissions(info.Permissions)
// print resources
printElevenLabsResources(info.ElevenLabsResources)
color.Yellow("\n[i] Expires: Never")
}
}
// secretInfoToAnalyzerResult translate secret info to Analyzer Result
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzers.AnalyzerTypeElevenLabs,
Metadata: map[string]any{},
Bindings: make([]analyzers.Binding, 0),
}
// for resources to be uniquely identified, we need a unique id to be appended in resource fully qualified name
uniqueId := info.User.ID
if uniqueId == "" {
uniqueId = uuid.NewString()
}
// extract information from resource to create bindings and append to result bindings
for _, resource := range info.ElevenLabsResources {
// if resource has permission it is binded resource
if resource.Permission != "" {
binding := analyzers.Binding{
Resource: analyzers.Resource{
Name: resource.Name,
FullyQualifiedName: fmt.Sprintf("%s/%s/%s", uniqueId, resource.Type, resource.ID), // e.g: <user_id>/Model/eleven_flash_v2_5
Type: resource.Type,
Metadata: map[string]any{}, // to avoid panic
},
Permission: analyzers.Permission{
Value: resource.Permission,
},
}
for key, value := range resource.Metadata {
binding.Resource.Metadata[key] = value
}
result.Bindings = append(result.Bindings, binding)
} else {
// if resource is missing permission it is an unbounded resource
unboundedResource := analyzers.Resource{
Name: resource.Name,
FullyQualifiedName: fmt.Sprintf("%s/%s/%s", uniqueId, resource.Type, resource.ID),
Type: resource.Type,
Metadata: map[string]any{},
}
for key, value := range resource.Metadata {
unboundedResource.Metadata[key] = value
}
result.UnboundedResources = append(result.UnboundedResources, unboundedResource)
}
}
result.Metadata["Valid_Key"] = info.Valid
return &result
}
// fetchUser fetch elevenlabs user information associated with the key
func fetchUser(client *http.Client, key string) (*User, error) {
response, statusCode, err := makeElevenLabsRequest(client, permissionToAPIMap[UserRead], http.MethodGet, key)
if err != nil {
return nil, err
}
switch statusCode {
case http.StatusOK:
var user UserResponse
if err := json.Unmarshal(response, &user); err != nil {
return nil, err
}
return &User{
ID: user.UserID,
Name: user.FirstName,
SubscriptionTier: user.Subscription.Tier,
SubscriptionStatus: user.Subscription.Status,
}, nil
case http.StatusUnauthorized:
var errorResp ErrorResponse
if err := json.Unmarshal(response, &errorResp); err != nil {
return nil, err
}
if errorResp.Detail.Status == InvalidAPIKey || errorResp.Detail.Status == NotVerifiable {
return nil, errors.New("invalid api key")
} else if errorResp.Detail.Status == MissingPermissions {
// key is missing user read permissions but is valid
return nil, nil
}
return nil, nil
default:
return nil, fmt.Errorf("unexpected status code: %d", statusCode)
}
}
// elevenLabsUserToSecretInfoUser set the elevenlabs user information to secretInfo user
func elevenLabsUserToSecretInfoUser(user User, secretInfo *SecretInfo) {
secretInfo.User = user
// add user read scope to secret info
secretInfo.Permissions = append(secretInfo.Permissions, PermissionStrings[UserRead])
// map resource to secret info
// as user is accessible through a specific permission and has a unique id it is also a resource
secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{
ID: user.ID,
Name: user.Name,
Type: "User",
Permission: PermissionStrings[UserRead],
})
}
/*
getElevenLabsResources gather resources the key can access
Note: The permissions in eleven labs is either Read or Read and Write. There is not separate permission for Write.
*/
func getElevenLabsResources(client *http.Client, key string, secretInfo *SecretInfo) error {
var (
aggregatedErrs = make([]string, 0)
errChan = make(chan error, 17) // buffer for 17 errors - one per API call
wg sync.WaitGroup
)
// history
wg.Add(1)
go func() {
defer wg.Done()
if err := getHistory(client, key, secretInfo); err != nil {
errChan <- err
}
if err := deleteHistory(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// dubbings
wg.Add(1)
go func() {
defer wg.Done()
if err := deleteDubbing(client, key, secretInfo); err != nil {
errChan <- err
}
// if dubbing write permission was not added
if !secretInfo.HasPermission(DubbingWrite) {
if err := getDebugging(client, key, secretInfo); err != nil {
errChan <- err
}
}
}()
// voices
wg.Add(1)
go func() {
defer wg.Done()
if err := getVoices(client, key, secretInfo); err != nil {
errChan <- err
}
if err := deleteVoice(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// projects
wg.Add(1)
go func() {
defer wg.Done()
if err := getProjects(client, key, secretInfo); err != nil {
errChan <- err
}
if err := deleteProject(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// pronunciation dictionaries
wg.Add(1)
go func() {
defer wg.Done()
if err := getPronunciationDictionaries(client, key, secretInfo); err != nil {
errChan <- err
}
if err := removePronunciationDictionariesRule(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// models
wg.Add(1)
go func() {
defer wg.Done()
if err := getModels(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// audio native
wg.Add(1)
go func() {
defer wg.Done()
if err := updateAudioNativeProject(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// workspace
wg.Add(1)
go func() {
defer wg.Done()
if err := deleteInviteFromWorkspace(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// speech
wg.Add(1)
go func() {
defer wg.Done()
if err := textToSpeech(client, key, secretInfo); err != nil {
errChan <- err
}
// voice changer
if err := speechToSpeech(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// audio isolation
wg.Add(1)
go func() {
defer wg.Done()
if err := audioIsolation(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// agent
wg.Add(1)
go func() {
defer wg.Done()
// each agent can have a conversations which we get inside this function
if err := getAgents(client, key, secretInfo); err != nil {
errChan <- err
}
}()
// wait for all API calls to finish
wg.Wait()
close(errChan)
// collect all errors
for err := range errChan {
aggregatedErrs = append(aggregatedErrs, err.Error())
}
if len(aggregatedErrs) > 0 {
return errors.New(strings.Join(aggregatedErrs, ", "))
}
return nil
}
// cli print functions
func printUser(user User) {
color.Green("\n[i] User:")
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"ID", "Name", "Subscription Tier", "Subscription Status"})
t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Name), color.GreenString(user.SubscriptionTier), color.GreenString(user.SubscriptionStatus)})
t.Render()
}
func printPermissions(permissions []string) {
color.Yellow("[i] Permissions:")
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Permission"})
for _, permission := range permissions {
t.AppendRow(table.Row{color.GreenString(permission)})
}
t.Render()
}
func printElevenLabsResources(resources []ElevenLabsResource) {
color.Green("\n[i] Resources:")
t := table.NewWriter()
t.SetOutputMirror(os.Stdout)
t.AppendHeader(table.Row{"Resource Type", "Resource ID", "Resource Name", "Permission"})
for _, resource := range resources {
t.AppendRow(table.Row{color.GreenString(resource.Type), color.GreenString(resource.ID), color.GreenString(resource.Name), color.GreenString(resource.Permission)})
}
t.Render()
}
@@ -0,0 +1,102 @@
package elevenlabs
import (
_ "embed"
"encoding/json"
"sort"
"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)
}
key := testSecrets.MustGetField("ELEVENLABS")
tests := []struct {
name string
key string
want []byte // JSON string
wantErr bool
}{
{
name: "valid ElevenLabs full access key",
key: key,
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.key})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}
// Bindings need to be in the same order to be comparable
sortBindings(got.Bindings)
// 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)
}
// Bindings need to be in the same order to be comparable
sortBindings(wantObj.Bindings)
// 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)
}
})
}
}
// Helper function to sort bindings
func sortBindings(bindings []analyzers.Binding) {
sort.SliceStable(bindings, func(i, j int) bool {
if bindings[i].Resource.Name == bindings[j].Resource.Name {
return bindings[i].Permission.Value < bindings[j].Permission.Value
}
return bindings[i].Resource.Name < bindings[j].Resource.Name
})
}
@@ -0,0 +1,151 @@
// Code generated by go generate; DO NOT EDIT.
package elevenlabs
import "errors"
type Permission int
const (
Invalid Permission = iota
TextToSpeech Permission = iota
SpeechToSpeech Permission = iota
AudioIsolation Permission = iota
DubbingRead Permission = iota
DubbingWrite Permission = iota
ProjectsRead Permission = iota
ProjectsWrite Permission = iota
AudioNativeRead Permission = iota
AudioNativeWrite Permission = iota
PronunciationDictionariesRead Permission = iota
PronunciationDictionariesWrite Permission = iota
VoicesRead Permission = iota
VoicesWrite Permission = iota
ModelsRead Permission = iota
SpeechHistoryRead Permission = iota
SpeechHistoryWrite Permission = iota
UserRead Permission = iota
WorkspaceRead Permission = iota
WorkspaceWrite Permission = iota
)
var (
PermissionStrings = map[Permission]string{
TextToSpeech: "text_to_speech",
SpeechToSpeech: "speech_to_speech",
AudioIsolation: "audio_isolation",
DubbingRead: "dubbing_read",
DubbingWrite: "dubbing_write",
ProjectsRead: "projects_read",
ProjectsWrite: "projects_write",
AudioNativeRead: "audio_native_read",
AudioNativeWrite: "audio_native_write",
PronunciationDictionariesRead: "pronunciation_dictionaries_read",
PronunciationDictionariesWrite: "pronunciation_dictionaries_write",
VoicesRead: "voices_read",
VoicesWrite: "voices_write",
ModelsRead: "models_read",
SpeechHistoryRead: "speech_history_read",
SpeechHistoryWrite: "speech_history_write",
UserRead: "user_read",
WorkspaceRead: "workspace_read",
WorkspaceWrite: "workspace_write",
}
StringToPermission = map[string]Permission{
"text_to_speech": TextToSpeech,
"speech_to_speech": SpeechToSpeech,
"audio_isolation": AudioIsolation,
"dubbing_read": DubbingRead,
"dubbing_write": DubbingWrite,
"projects_read": ProjectsRead,
"projects_write": ProjectsWrite,
"audio_native_read": AudioNativeRead,
"audio_native_write": AudioNativeWrite,
"pronunciation_dictionaries_read": PronunciationDictionariesRead,
"pronunciation_dictionaries_write": PronunciationDictionariesWrite,
"voices_read": VoicesRead,
"voices_write": VoicesWrite,
"models_read": ModelsRead,
"speech_history_read": SpeechHistoryRead,
"speech_history_write": SpeechHistoryWrite,
"user_read": UserRead,
"workspace_read": WorkspaceRead,
"workspace_write": WorkspaceWrite,
}
PermissionIDs = map[Permission]int{
TextToSpeech: 1,
SpeechToSpeech: 2,
AudioIsolation: 3,
DubbingRead: 4,
DubbingWrite: 5,
ProjectsRead: 6,
ProjectsWrite: 7,
AudioNativeRead: 8,
AudioNativeWrite: 9,
PronunciationDictionariesRead: 10,
PronunciationDictionariesWrite: 11,
VoicesRead: 12,
VoicesWrite: 13,
ModelsRead: 14,
SpeechHistoryRead: 15,
SpeechHistoryWrite: 16,
UserRead: 17,
WorkspaceRead: 18,
WorkspaceWrite: 19,
}
IdToPermission = map[int]Permission{
1: TextToSpeech,
2: SpeechToSpeech,
3: AudioIsolation,
4: DubbingRead,
5: DubbingWrite,
6: ProjectsRead,
7: ProjectsWrite,
8: AudioNativeRead,
9: AudioNativeWrite,
10: PronunciationDictionariesRead,
11: PronunciationDictionariesWrite,
12: VoicesRead,
13: VoicesWrite,
14: ModelsRead,
15: SpeechHistoryRead,
16: SpeechHistoryWrite,
17: UserRead,
18: WorkspaceRead,
19: WorkspaceWrite,
}
)
// 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,24 @@
permissions:
- text_to_speech
- speech_to_speech
# - sound_generation
- audio_isolation
# - voice_generation
- dubbing_read
- dubbing_write
- projects_read
- projects_write
- audio_native_read
- audio_native_write
- pronunciation_dictionaries_read
- pronunciation_dictionaries_write
- voices_read
- voices_write
- models_read
# - models_write
- speech_history_read
- speech_history_write
- user_read
# - user_write
- workspace_read
- workspace_write
@@ -0,0 +1,782 @@
package elevenlabs
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"slices"
"strings"
)
// permissionToAPIMap contain the API endpoints for each scope/permission
// api docs: https://elevenlabs.io/docs/api-reference/introduction
var permissionToAPIMap = map[Permission]string{
TextToSpeech: "https://api.elevenlabs.io/v1/text-to-speech/%s", // require voice id
SpeechToSpeech: "https://api.elevenlabs.io/v1/speech-to-speech/%s", // require voice id
AudioIsolation: "https://api.elevenlabs.io/v1/audio-isolation",
DubbingRead: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id
DubbingWrite: "https://api.elevenlabs.io/v1/dubbing/%s", // require dubbing id
ProjectsRead: "https://api.elevenlabs.io/v1/projects",
ProjectsWrite: "https://api.elevenlabs.io/v1/projects/%s", // require project id
AudioNativeWrite: "https://api.elevenlabs.io/v1/audio-native/%s/content", // require project id
PronunciationDictionariesRead: "https://api.elevenlabs.io/v1/pronunciation-dictionaries",
PronunciationDictionariesWrite: "https://api.elevenlabs.io/v1/pronunciation-dictionaries/%s/remove-rules", // require pronunciation dictionary id
VoicesRead: "https://api.elevenlabs.io/v1/voices",
VoicesWrite: "https://api.elevenlabs.io/v1/voices/%s", // require voice id
ModelsRead: "https://api.elevenlabs.io/v1/models",
SpeechHistoryRead: "https://api.elevenlabs.io/v1/history",
SpeechHistoryWrite: "https://api.elevenlabs.io/v1/history/%s", // require history item id
UserRead: "https://api.elevenlabs.io/v1/user",
WorkspaceWrite: "https://api.elevenlabs.io/v1/workspace/invites",
}
var (
// not exist key
fakeID = "_thou_shalt_not_exist_"
// error statuses
NotVerifiable = "api_key_not_verifiable"
InvalidAPIKey = "invalid_api_key"
MissingPermissions = "missing_permissions"
DubbingNotFound = "dubbing_not_found"
ProjectNotFound = "project_not_found"
VoiceDoesNotExist = "voice_does_not_exist"
InvalidSubscription = "invalid_subscription"
PronunciationDictionaryNotFound = "pronunciation_dictionary_not_found"
InternalServerError = "internal_server_error"
InvalidProjectID = "invalid_project_id"
ModelNotFound = "model_not_found"
VoiceNotFound = "voice_not_found"
InvalidContent = "invalid_content"
)
// ErrorResponse is the error response for all APIs
type ErrorResponse struct {
Detail struct {
Status string `json:"status"`
} `json:"detail"`
}
// UserResponse is the /user API response
type UserResponse struct {
UserID string `json:"user_id"`
FirstName string `json:"first_name"`
Subscription struct {
Tier string `json:"tier"`
Status string `json:"status"`
} `json:"subscription"`
}
// HistoryResponse is the /history API response
type HistoryResponse struct {
History []struct {
ID string `json:"history_item_id"`
ModelID string `json:"model_id"`
VoiceID string `json:"voice_id"`
} `json:"history"`
}
// VoiceResponse is the /voices API response
type VoicesResponse struct {
Voices []struct {
ID string `json:"voice_id"`
Name string `json:"name"`
Category string `json:"category"`
} `json:"voices"`
}
// ProjectsResponse is the /projects API response
type ProjectsResponse struct {
Projects []struct {
ID string `json:"project_id"`
Name string `json:"name"`
State string `json:"state"`
AccessLevel string `json:"access_level"`
} `json:"projects"`
}
// PronunciationDictionaries is the /pronunciation-dictionaries API response
type PronunciationDictionariesResponse struct {
PronunciationDictionaries []struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"pronunciation_dictionaries"`
}
// Models is the /models API response
type ModelsResponse struct {
ID string `json:"model_id"`
Name string `json:"name"`
}
// AgentsResponse is the /agents API response
type AgentsResponse struct {
Agents []struct {
ID string `json:"agent_id"`
Name string `json:"name"`
AccessLevel string `json:"access_level"`
} `json:"agents"`
}
// ConversationResponse is the /conversation API response
type ConversationResponse struct {
Conversations []struct {
AgentID string `json:"agent_id"`
ID string `json:"conversation_id"`
Status string `json:"status"`
}
}
// getAPIUrl return the API Url mapped to the permission
func getAPIUrl(permission Permission) string {
apiUrl := permissionToAPIMap[permission]
if strings.Contains(apiUrl, "%s") {
return fmt.Sprintf(apiUrl, fakeID)
}
return apiUrl
}
// makeElevenLabsRequest send the API request to passed url with passed key as API Key and return response body and status code
func makeElevenLabsRequest(client *http.Client, url, method, key string) ([]byte, int, error) {
// create request
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil {
return nil, 0, err
}
// add key in the header
req.Header.Add("xi-api-key", key)
resp, err := client.Do(req)
if err != nil {
return nil, 0, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
/*
the reason to translate body to byte and does not directly return http.Response
is if we return http.Response we cannot close the body in defer. If we do we will get an error
when reading body outside this function
*/
responseBodyByte, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, err
}
return responseBodyByte, resp.StatusCode, nil
}
// makeElevenLabsRequestWithPayload sends a POST/PATCH API request to the passed URL with the given key as the API Key
// and an optional payload. It returns the response body and status code.
func makeElevenLabsRequestWithPayload(client *http.Client, url, method, contentType, key string, payload []byte) ([]byte, int, error) {
// Create request with payload
req, err := http.NewRequest(method, url, bytes.NewBuffer(payload))
if err != nil {
return nil, 0, err
}
// Add headers
req.Header.Add("xi-api-key", key)
req.Header.Add("Content-Type", contentType)
// Send the request
resp, err := client.Do(req)
if err != nil {
return nil, 0, err
}
// ensure the response body is properly closed
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
// read the response body
responseBodyByte, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, err
}
return responseBodyByte, resp.StatusCode, nil
}
// getHistory get history item using the key passed and add them to secret info
func getHistory(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var history HistoryResponse
if err := json.Unmarshal(response, &history); err != nil {
return err
}
// add history read scope to secret info
secretInfo.AppendPermission(PermissionStrings[SpeechHistoryRead])
// map resource to secret info
for _, historyItem := range history.History {
secretInfo.AppendResource(ElevenLabsResource{
ID: historyItem.ID,
Name: "", // no name
Type: "History",
Permission: PermissionStrings[SpeechHistoryRead],
})
}
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking history read scope", statusCode)
}
}
// deleteHistory try to delete a history item. The item must not exist.
func deleteHistory(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(SpeechHistoryWrite), http.MethodDelete, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusInternalServerError:
// for some reason if we send fake id and token has the permission, the history api return 500 error instead of 404
// issue opened in elevenlabs-docs: https://github.com/elevenlabs/elevenlabs-docs/issues/649
return handleErrorStatus(response, PermissionStrings[SpeechHistoryWrite], secretInfo, InternalServerError)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking history write scope", statusCode)
}
}
// deleteDubbing try to delete a dubbing. The item must not exist.
func deleteDubbing(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingWrite), http.MethodDelete, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusNotFound:
// as we send fake id, if permission is assigned to token we must get 404 dubbing not found
if err := handleErrorStatus(response, PermissionStrings[DubbingWrite], secretInfo, DubbingNotFound); err != nil {
return err
}
// add read scope of dubbing to avoid get dubbing api call
secretInfo.AppendPermission(PermissionStrings[DubbingRead])
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking dubbing write scope", statusCode)
}
}
// getDebugging try to get a dubbing. The item must not exist.
func getDebugging(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(DubbingRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusNotFound:
// as we send fake id, if permission is assigned to token we must get 404 dubbing not found
return handleErrorStatus(response, PermissionStrings[DubbingRead], secretInfo, DubbingNotFound)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking dubbing read scope", statusCode)
}
}
// getVoices get list of voices using the key passed and add them to secret info
func getVoices(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var voices VoicesResponse
if err := json.Unmarshal(response, &voices); err != nil {
return err
}
// add voices read scope to secret info
secretInfo.AppendPermission(PermissionStrings[VoicesRead])
// map resource to secret info
for _, voice := range voices.Voices {
secretInfo.AppendResource(ElevenLabsResource{
ID: voice.ID,
Name: voice.Name,
Type: "Voice",
Permission: PermissionStrings[VoicesRead],
Metadata: map[string]string{
"category": voice.Category,
},
})
}
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking voice read scope", statusCode)
}
}
// deleteVoice try to delete a voice. The item must not exist.
func deleteVoice(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(VoicesWrite), http.MethodDelete, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
// if permission was assigned to scope we should get 400 error with voice not found status
return handleErrorStatus(response, PermissionStrings[VoicesWrite], secretInfo, VoiceDoesNotExist)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking voice write scope", statusCode)
}
}
// getProjects get list of projects using the key passed and add them to secret info
func getProjects(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var projects ProjectsResponse
if err := json.Unmarshal(response, &projects); err != nil {
return err
}
// add project read scope to secret info
secretInfo.AppendPermission(PermissionStrings[ProjectsRead])
// map resource to secret info
for _, project := range projects.Projects {
secretInfo.AppendResource(ElevenLabsResource{
ID: project.ID,
Name: project.Name,
Type: "Project",
Permission: PermissionStrings[ProjectsRead],
Metadata: map[string]string{
"state": project.State,
"access level": project.AccessLevel, // access level of project
},
})
}
return nil
case http.StatusForbidden:
// if token has the permission but trail is free, projects are not accessible
return handleErrorStatus(response, PermissionStrings[ProjectsRead], secretInfo, InvalidSubscription)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking projects read scope", statusCode)
}
}
// deleteProject try to delete a project. The item must not exist.
func deleteProject(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ProjectsWrite), http.MethodDelete, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
// if permission was assigned to token we should get 400 error with project not found status
return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, ProjectNotFound)
case http.StatusForbidden:
// if token has the permission but trail is free, projects are not accessible
return handleErrorStatus(response, PermissionStrings[ProjectsWrite], secretInfo, InvalidSubscription)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking project write scope", statusCode)
}
}
// getPronunciationDictionaries get list of pronunciation dictionaries using the key passed and add them to secret info
func getPronunciationDictionaries(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(PronunciationDictionariesRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var PDs PronunciationDictionariesResponse
if err := json.Unmarshal(response, &PDs); err != nil {
return err
}
// add voices read scope to secret info
secretInfo.AppendPermission(PermissionStrings[PronunciationDictionariesRead])
// map resource to secret info
for _, pd := range PDs.PronunciationDictionaries {
secretInfo.AppendResource(ElevenLabsResource{
ID: pd.ID,
Name: pd.Name,
Type: "Pronunciation Dictionary",
Permission: PermissionStrings[PronunciationDictionariesRead],
})
}
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking pronunciation dictionaries read scope", statusCode)
}
}
// removePronunciationDictionariesRule try to remove a rule from pronunciation dictionaries. The item must not exist.
func removePronunciationDictionariesRule(client *http.Client, key string, secretInfo *SecretInfo) error {
// send empty list of rule strings
payload := map[string]interface{}{
"rule_strings": []string{""},
}
payloadBytes, _ := json.Marshal(payload)
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(PronunciationDictionariesWrite), http.MethodPost,
"application/json", key, payloadBytes)
if err != nil {
return err
}
switch statusCode {
case http.StatusNotFound:
// if permission was assigned to token we should get 404 error with pronunciation_dictionary_not_found status
return handleErrorStatus(response, PermissionStrings[PronunciationDictionariesWrite], secretInfo, PronunciationDictionaryNotFound)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking pronunciation dictionary write scope", statusCode)
}
}
// getModels list models using the key passed and add them to secret info
func getModels(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, getAPIUrl(ModelsRead), http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var models []ModelsResponse
if err := json.Unmarshal(response, &models); err != nil {
return err
}
// add models read scope to secret info
secretInfo.AppendPermission(PermissionStrings[ModelsRead])
// map resource to secret info
for _, model := range models {
secretInfo.AppendResource(ElevenLabsResource{
ID: model.ID,
Name: model.Name,
Type: "Model",
Permission: PermissionStrings[ModelsRead],
})
}
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
}
}
// updateAudioNativeProject try to update a project content. The item must not exist.
func updateAudioNativeProject(client *http.Client, key string, secretInfo *SecretInfo) error {
// create a buffer to hold the multipart form data
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// add required fields to multipart form body
_ = writer.WriteField("auto_convert", "false")
_ = writer.WriteField("auto_publish", "false")
// close the writer
_ = writer.Close()
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioNativeWrite), http.MethodPost,
writer.FormDataContentType(), key, body.Bytes())
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
// if the permission is assigned to token, the api should return 400 with invalid project id
if err := handleErrorStatus(response, PermissionStrings[AudioNativeWrite], secretInfo, InvalidProjectID); err != nil {
return err
}
// add read permission as no separate API exist to check read audio native permission
secretInfo.AppendPermission(PermissionStrings[AudioNativeRead])
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking audio native write scope", statusCode)
}
}
// deleteInviteFromWorkspace try to remove a invite from workspace. The item must not exist.
func deleteInviteFromWorkspace(client *http.Client, key string, secretInfo *SecretInfo) error {
// send fake email in payload
payload := map[string]interface{}{
"email": fakeID + "@example.com",
}
payloadBytes, _ := json.Marshal(payload)
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(WorkspaceWrite), http.MethodDelete,
"application/json", key, payloadBytes)
if err != nil {
return err
}
switch statusCode {
case http.StatusInternalServerError:
// for some reason if we send fake email and token has the permission, the workspace invite api return 500 error instead of 404
if err := handleErrorStatus(response, PermissionStrings[WorkspaceWrite], secretInfo, InternalServerError); err != nil {
return err
}
// add read permission as no separate API exist to check workspace read permission
secretInfo.AppendPermission(PermissionStrings[WorkspaceRead])
return nil
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking workspace write scope", statusCode)
}
}
// textToSpeech try to convert text to speech. The model id and voice id is fake so it actually never happens.
func textToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error {
// send fake model id in payload
payload := map[string]interface{}{
"text": "This is trufflehog trying to check text to speech permission of the token",
"model_id": fakeID,
}
payloadBytes, _ := json.Marshal(payload)
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(TextToSpeech), http.MethodPost,
"application/json", key, payloadBytes)
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
// if permission is assigned to token, error status will be either model not found or voice not found as we sent both fake ;)
return handleErrorStatus(response, PermissionStrings[TextToSpeech], secretInfo, ModelNotFound, VoiceNotFound)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking text to speech scope", statusCode)
}
}
// speechToSpeech try to change a voice in speech. The model id and voice id is fake so it actually never happens.
func speechToSpeech(client *http.Client, key string, secretInfo *SecretInfo) error {
// create a buffer to hold the multipart form data
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// add required fields to multipart form body
_ = writer.WriteField("model_id", fakeID)
_ = writer.WriteField("seed", "1")
_ = writer.WriteField("remove_background_noise", "false")
audio, _ := writer.CreateFormFile("audio", "")
_, _ = audio.Write([]byte("This is example fake audio for api call"))
// close the writer
_ = writer.Close()
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(SpeechToSpeech), http.MethodPost,
writer.FormDataContentType(), key, body.Bytes())
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
return handleErrorStatus(response, PermissionStrings[SpeechToSpeech], secretInfo, InvalidContent)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking speech to speech scope", statusCode)
}
}
// audioIsolation try to remove background noise from a voice. The file will be corrupted so it should return an error.
func audioIsolation(client *http.Client, key string, secretInfo *SecretInfo) error {
// create a buffer to hold the multipart form data
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
audio, _ := writer.CreateFormFile("audio", "")
_, _ = audio.Write([]byte("This is example fake audio for api call"))
// close the writer
_ = writer.Close()
response, statusCode, err := makeElevenLabsRequestWithPayload(client, getAPIUrl(AudioIsolation), http.MethodPost,
writer.FormDataContentType(), key, body.Bytes())
if err != nil {
return err
}
switch statusCode {
case http.StatusBadRequest:
return handleErrorStatus(response, PermissionStrings[AudioIsolation], secretInfo, InvalidContent)
case http.StatusUnauthorized:
return handleErrorStatus(response, "", secretInfo, MissingPermissions)
default:
return fmt.Errorf("unexpected status code: %d while checking audio isolation speech scope", statusCode)
}
}
/*
getAgents get all user agents which are not bound with any permission
call APIs in pattern: agents->conversation
*/
func getAgents(client *http.Client, key string, secretInfo *SecretInfo) error {
response, statusCode, err := makeElevenLabsRequest(client, "https://api.elevenlabs.io/v1/convai/agents", http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var agents AgentsResponse
if err := json.Unmarshal(response, &agents); err != nil {
return err
}
// map resource to secret info
for _, agent := range agents.Agents {
resource := ElevenLabsResource{
ID: agent.ID,
Name: agent.Name,
Type: "Agent",
Permission: "", // not binded with any permission
Metadata: map[string]string{
"access level": agent.AccessLevel,
},
}
secretInfo.AppendResource(resource)
// get agent conversations
if err := getConversation(client, key, agent.ID, secretInfo); err != nil {
return err
}
}
return nil
default:
return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
}
}
// getConversation list all agent conversations using the key and agentID passed and add them to secret info
func getConversation(client *http.Client, key, agentID string, secretInfo *SecretInfo) error {
apiUrl := fmt.Sprintf("https://api.elevenlabs.io/v1/convai/conversations?agent_id=%s", agentID)
response, statusCode, err := makeElevenLabsRequest(client, apiUrl, http.MethodGet, key)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var conversations ConversationResponse
if err := json.Unmarshal(response, &conversations); err != nil {
return err
}
// map resource to secret info
for _, conversation := range conversations.Conversations {
secretInfo.AppendResource(ElevenLabsResource{
ID: conversation.ID,
Name: "", // no name
Type: "Conversation",
Permission: "", // not binded with any permission
Metadata: map[string]string{
"status": conversation.Status,
},
})
}
return nil
default:
return fmt.Errorf("unexpected status code: %d while checking models read scope", statusCode)
}
}
// handleErrorStatus handle error response, check if expected error status is in the response and add require permission to secret info
// this is used in case where we expect error response with specific status mostly in write calls
func handleErrorStatus(response []byte, permissionToAdd string, secretInfo *SecretInfo, expectedErrStatuses ...string) error {
// check if status in response is what is expected to be
ok, err := checkErrorStatus(response, expectedErrStatuses...)
if err != nil {
return err
}
// if permission to add was passed and it was expected error status add the permission
if permissionToAdd != "" && ok {
secretInfo.AppendPermission(permissionToAdd)
} else if permissionToAdd != "" && !ok {
// if permission to add was passed and it was unexpected error status - return error
return errors.New("unexpected error response")
}
return nil
}
// checkErrorStatus check if any of expected error status exist in actual API error response
func checkErrorStatus(response []byte, expectedStatuses ...string) (bool, error) {
var errorResp ErrorResponse
if err := json.Unmarshal(response, &errorResp); err != nil {
return false, err
}
if slices.Contains(expectedStatuses, errorResp.Detail.Status) {
return true, nil
}
return false, nil
}
@@ -0,0 +1,439 @@
{
"AnalyzerType": 6,
"Bindings": [
{
"Resource": {
"Name": "Ahmed",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/User/b9Rou9mHDmTYd8cdWkg2Yk4P2lq1",
"Type": "User",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "user_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Alice",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/Xb7hH8MSUJpSbSDYk0k2",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Aria",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/9BWtsMINqrJLrRacOk9x",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Bill",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pqHfZKP75CvOlQylNhV4",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Brian",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/nPczCjzI2devNBz1zQrb",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Callum",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/N2lVS1w4EtoT3dr4eOWO",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Charlie",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/IKne3meq5aSn9XLyUdCD",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Charlotte",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XB0fDUnXU5powFXDhCwa",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Chris",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/iP95p4xoKVk53GoZ742B",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Daniel",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/onwK4e9ZLuTAKqWW03F9",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven English v1",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_monolingual_v1",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven English v2",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_english_sts_v2",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven Flash v2",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven Flash v2.5",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_flash_v2_5",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven Multilingual v1",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v1",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven Multilingual v2",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_v2",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven Multilingual v2",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_multilingual_sts_v2",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven Turbo v2",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eleven Turbo v2.5",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Model/eleven_turbo_v2_5",
"Type": "Model",
"Metadata": {},
"Parent": null
},
"Permission": {
"Value": "models_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Eric",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cjVigY5qzO86Huf0OWal",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "George",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/JBFqnCBsd6RMkjVDRZzb",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Jessica",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/cgSgspJ2msm6clMCkdW9",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Laura",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/FGY2WhTYpPnrIDTdsKH5",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Liam",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/TX3LPaxmHKxFdv7VOQHJ",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Lily",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/pFZP5JQG7iQjIQuC4Bku",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Matilda",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/XrExE9yKIg1WjnnlVkGX",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "River",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/SAz9YHcvj6GT2YYXdXww",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Roger",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/CwhRBWXzGAHq8TQ4Fs17",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Sarah",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/EXAVITQu4vr4xnSDxMaL",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
},
{
"Resource": {
"Name": "Will",
"FullyQualifiedName": "b9Rou9mHDmTYd8cdWkg2Yk4P2lq1/Voice/bIHbv24MWmeRgasZH58o",
"Type": "Voice",
"Metadata": {
"category": "premade"
},
"Parent": null
},
"Permission": {
"Value": "voices_read",
"Parent": null
}
}
],
"UnboundedResources": null,
"Metadata": {
"Valid_Key": true
}
}
+3
View File
@@ -12,6 +12,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/digitalocean"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dockerhub"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/elevenlabs"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/github"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/gitlab"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/huggingface"
@@ -100,5 +101,7 @@ func Run(keyType string, secretInfo SecretInfo) {
airtable.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
case "digitalocean":
digitalocean.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
case "elevenlabs":
elevenlabs.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
}
}
@@ -74,6 +74,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1.ExtraData["Tier"] = userResponse.Subscription.Tier
}
s1.SetVerificationError(verificationErr, match)
if s1.Verified {
s1.AnalysisInfo = map[string]string{
"key": match,
}
}
}
results = append(results, s1)
@@ -71,6 +71,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
s1.ExtraData["Tier"] = userResponse.Subscription.Tier
}
s1.SetVerificationError(verificationErr, match)
if s1.Verified {
s1.AnalysisInfo = map[string]string{
"key": match,
}
}
}
results = append(results, s1)
@@ -54,7 +54,7 @@ func TestElevenlabs_FromChunk(t *testing.T) {
Verified: true,
ExtraData: map[string]string{
"version": "2",
"Name": "Ahmed",
"Name": "Trufflesecurity",
"Tier": "free",
},
},
@@ -150,7 +150,7 @@ func TestElevenlabs_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("Elevenlabs.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}