Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

elevenlabs analyzer #3850

Merged

Conversation

kashifkhan0771
Copy link
Contributor

@kashifkhan0771 kashifkhan0771 commented Jan 23, 2025

Description:

This Pull requests add a new analyzer for ElevenLabs

Test Cases:

Screenshot from 2025-01-28 11-38-10
Screenshot from 2025-01-28 12-36-36

Checklist:

  • Tests passing (make test-community)?
  • Lint passing (make lint this requires golangci-lint)?

@kashifkhan0771 kashifkhan0771 marked this pull request as ready for review January 27, 2025 14:33
@kashifkhan0771 kashifkhan0771 requested a review from a team as a code owner January 27, 2025 14:33
@kashifkhan0771 kashifkhan0771 changed the title elevenlabs analyzer [WIP] elevenlabs analyzer Jan 27, 2025
@kashifkhan0771 kashifkhan0771 requested a review from a team as a code owner January 28, 2025 07:43
If write permission API calls was not as expected than only we make read permission API calls
This we only do for those API calls which does not add any resources to secretInfo
*/
func getResources(client *http.Client, key string, secretInfo *SecretInfo) error {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Points to Consider for Implementation while getting resource:

  1. Aggregate Errors:
    Instead of failing on the first error, collect all errors encountered during the process and return them as a single aggregated error at the end. This ensures that users get a complete picture of what went wrong, rather than having to address issues one at a time.

  2. Graceful Error Handling in CLI:
    For the CLI, log errors when checking a specific scope or fetching resource fails, but continue processing other tasks. Improving the user experience and allowing for partial success.

  3. Concurrent API Calls:
    Use Go routines to call APIs concurrently. This will significantly improve performance by reducing the total time spent waiting for API responses.

Let me know your thoughts on these points.

Copy link
Contributor

Choose a reason for hiding this comment

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

My vote will be for concurrent API calls with error handling for each permission.

If write permission API calls was not as expected than only we make read permission API calls
This we only do for those API calls which does not add any resources to secretInfo
*/
func getResources(client *http.Client, key string, secretInfo *SecretInfo) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

My vote will be for concurrent API calls with error handling for each permission.

@kashifkhan0771 kashifkhan0771 force-pushed the feat/oss-97-elevenlabs-analyzer branch from 3ba85a9 to 4975781 Compare January 29, 2025 14:04
Copy link
Collaborator

@ahrav ahrav left a comment

Choose a reason for hiding this comment

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

Couple questions inline, and requested changes due to the race condition. Otherwise, nice work!

Copy link
Collaborator

@ahrav ahrav left a comment

Choose a reason for hiding this comment

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

Approving so I don't block your ability to merge once the changes are addressed.

Comment on lines +237 to +241
secretInfo.ElevenLabsResources = append(secretInfo.ElevenLabsResources, ElevenLabsResource{
ID: user.ID,
Name: user.Name,
Type: "User",
Permission: PermissionStrings[UserRead],
Copy link
Contributor

Choose a reason for hiding this comment

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

As per my understanding, resource identification is related where Resoruce/Permission mapping is decided. We can move it to Analyze(...) func

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is called from AnalyzePermissions(). The permission mapping here is not for the analyzer resource binding but for the ElevenLabsResource Permission, which we add to secretInfo for later use. The actual permission binding occurs in secretInfoToAnalyzerResult, within Analyze().


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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

chore -- @kashifkhan0771 can this be simplified using json structure ? e.g. Opsgenie Analyzer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I considered that too, but for this analyzer, we need to treat multiple API responses as resources and store them in secretInfo. Opsgenie only checks whether an API permission exists from status code, which we can handle through JSON.

However, mapping all APIs to JSON would add complexity in defining which structure handles which API response and how different responses are mapped to secretInfo.

If you look at APIMAP in requests.go, you'll notice that some APIs only require a status code check without processing the response. Mixing JSON-based handling with direct handling could cause confusion, so I decided to use a consistent approach for all APIs.

// SecretInfo hold information about key
type SecretInfo struct {
User User // the owner of key
Valid bool
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of this boolean? at some places, SecretInfo was being checked as nil. Does that conditions represent the validity of API keys ? If yes, Valid boolean seems redundant to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Honestly speaking, I keep it just to be consistent with other analyzers. This bool is marked as true once we pass the fetchUser step.

Reference string
Permissions []string // list of Permissions assigned to the key
ElevenLabsResources []ElevenLabsResource // list of resources the key has access to
Misc map[string]string
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not see any use of this Property.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same here. This was my first analyzer, so I aimed for consistency with the others. We can remove it if it's not necessary, but at a higher level, I believe we should maintain some consistency in SecretInfo across analyzers.

Comment on lines +135 to +137
if strings.Contains(apiUrl, "%s") {
return fmt.Sprintf(apiUrl, fakeID)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

why not add the fakeID's value in the url directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess I just came up with a fancy way to handle API endpoints from permissionToAPIMap 😄.
Now that I see your comment, it makes sense to add fakeID directly to the API endpoint.

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)
Copy link
Contributor

Choose a reason for hiding this comment

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

does 500 response shows that credentials has the permission to perform the action?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, ideally, it should return 400 or 404 since the ID shouldn't exist. We rely on an error response to confirm that the permission exists while the ID does not. However, this API was returning a 500 error instead. I opened an issue in their repository, and they acknowledged it, but I'm not sure when they'll fix it.

Comment on lines +252 to +261
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)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I might be overseeing but all the conditions are representing the error cases. What is the success condition ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we expect an error response for APIs where we attempt a write operation using a fakeID. This helps determine whether we have the required permission. If the permission is missing, we receive a 401 error; otherwise, we may get 400, 401, or in some cases, a 500 error with a specific message.

Comment on lines +353 to +360
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)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Same, no condition to mark successful permission

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above.

Comment on lines +413 to +423
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)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

same, no condition of success.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added comments to explain which error condition is considered as a success.

Comment on lines +33 to +34
Permissions []string // list of Permissions assigned to the key
ElevenLabsResources []ElevenLabsResource // list of resources the key has access to
Copy link
Contributor

Choose a reason for hiding this comment

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

in each go routine, Append operation is performed on same array, which is not thread safe. Might see varying results or race conditions if ran with multiple CPU core.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is handled in getElevenLabsResources function using Mutexes

Copy link
Collaborator

@ahrav ahrav Feb 21, 2025

Choose a reason for hiding this comment

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

This approach works, but I'd argue it's generally best to avoid holding mutexes during API calls, or any I/O operations for that matter. Doing so blocks other goroutines from making progress. We should minimize the critical section that requires synchronization—specifically, the concurrent updates to the shared underlying slice.

Another risk of holding a mutex during an API call is that if the call hangs or doesn’t respond (and there’s no timeout on the context), the system could end up deadlocked.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in each go routine, Append operation is performed on same array, which is not thread safe. Might see varying results or race conditions if ran with multiple CPU core.

This approach works, but I'd argue it's generally best to avoid holding mutexes during API calls, or any I/O operations for that matter. Doing so blocks other goroutines from making progress. We should minimize the critical section that requires synchronization—specifically, the concurrent updates to the shared underlying slice.

Added thread-safe methods in SecretInfo to append and read permissions, as well as to append new resources.
Tested locally, and it works fine without any race conditions. The test case is also passing successfully.

@kashifkhan0771
Copy link
Contributor Author

Sorry for the delay on this PR! This was the first one opened for the new analyzers from me, but I was also working on other analyzers that got merged earlier. This analyzer have some complexity and had some outstanding questions, so I hadn’t been focusing on it as much as the others. I’ll address all comments as soon as possible. Thanks for your patience!

Copy link
Collaborator

@ahrav ahrav left a comment

Choose a reason for hiding this comment

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

Nice work! This was definitely a pretty large analyzer.

@ahrav ahrav dismissed abmussani’s stale review February 25, 2025 15:59

Dismissing as the changes have been addressed.

@ahrav ahrav merged commit f7ff998 into trufflesecurity:main Feb 25, 2025
13 checks passed
@kashifkhan0771 kashifkhan0771 deleted the feat/oss-97-elevenlabs-analyzer branch February 26, 2025 05:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants