-
Notifications
You must be signed in to change notification settings - Fork 186
[ExceptionHandling] Throw proper 400 errors instead of 500 for agent execute and MCP #3988
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
[ExceptionHandling] Throw proper 400 errors instead of 500 for agent execute and MCP #3988
Conversation
Signed-off-by: Pavan Yekbote <pybot@amazon.com>
| } | ||
|
|
||
| public static <S> S initConnector(String name, Object[] initArgs, Class<?>... constructorParameterTypes) { | ||
| public static <S> S initConnector(String name, Object[] initArgs, Class<?>... constructorParameterTypes) throws JsonParseException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need to add throws JsonParseException?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the internal init method is throwing this exception, so this needs to be as well.
Now we are throwing new type of exception rather than simply error logging and returning null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the internal init method is throwing this exception
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For example:
POST /_plugins/_ml/agents/<agent_id>/_execute
{
"parameters": {
"question": "Give me statistics about my cluster",
"is_async": random
}
}
Here, we want to surface the actual error. In this case, is_async has invalid value, so that triggers the JsonParseException which we want to show to the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to understand :
if (cause instanceof MLException) {
throw (MLException) cause;
} else if (cause instanceof IllegalArgumentException) {
throw (IllegalArgumentException) cause;
why we don't need to add MLException or IllegalArgumentException in throws?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, got it. MLException and IllegalArgumentException are runtime exceptions so they don't need explicit throws.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrecognized token 'asjkdhsadkjfh': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n at [Source: REDACTED (
StreamReadFeature.INCLUDE_SOURCE_IN_LOCATIONdisabled); line: 4, column: 36]
I understand it's better to send a json parse exception as a final defense. But I think we should send validation error before even coming here. I assume this value is optional. But if the value is provided then we should check if this is a valid value. If not send a validation error.
If I'm a customer, from this error nothing makes sense to me except I just know a json parse error happened somewhere. And I don't have any direction how can I fix my issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, we should have some validation before even reaching this point.
This PR, i'm just ensuring we don't throw 500 errors to the user as this was raised as a security concern. It can be used to exploit codebase and expose unhandled paths.
As a last defense, I think this is good enough to understand the issue: it is pointing out which is the invalid token
Unrecognized token 'asjkdhsadkjfh': was expecting (JSON String, Number, Array, Object or token 'null', 'true' or 'false')\n
As you rightly mentioned, the correct solution would be to add validation to the input before executing any kind of task.
I created an issue to add proper validation for different kinds of executes depending on the Function being executed: #4013
we can track that there, I would like to avoid this here and immediately prevent potential exploits as a quick fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine if you want to work on a separate PR. But let's make sure we are working on that issue too. I still think this is a broken CX experience.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3988 +/- ##
============================================
+ Coverage 80.62% 80.63% +0.01%
- Complexity 8003 8007 +4
============================================
Files 695 695
Lines 35023 35025 +2
Branches 3935 3936 +1
============================================
+ Hits 28236 28243 +7
+ Misses 5051 5046 -5
Partials 1736 1736
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| .format(Locale.ROOT, "Unable to register tools: %s as they already exist", existingTools); | ||
| log.warn(exceptionMessage); | ||
| restoreListener.onFailure(new OpenSearchException(exceptionMessage)); | ||
| restoreListener.onFailure(new IllegalArgumentException(exceptionMessage)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, since the tool already exists, we can confidently say that it is an IllegalArgumentException as the name provided is already used.
| throw (MLException) cause; | ||
| } else if (cause instanceof IllegalArgumentException) { | ||
| throw (IllegalArgumentException) cause; | ||
| } else if (cause instanceof JsonParseException) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please explain in PR description, how's this fix handles the actual issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added more details to the description regarding the responses before and after this change
|
|
||
| @Test(expected = IllegalArgumentException.class) | ||
| public void testConnectorInitializationException() { | ||
| public void testConnectorInitializationException() throws JsonParseException { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the test case which actually throws JsonParseException, the actual scenario which you are fixing this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pyek-bot you should be able to write a test like this:
@Test
public void testJsonParseExceptionHandling() throws Exception {
// Add a field to access connectorClassMap for testing
Field connectorClassMapField = MLCommonsClassLoader.class.getDeclaredField("connectorClassMap");
connectorClassMapField.setAccessible(true);
@SuppressWarnings("unchecked")
Map<String, Class<?>> connectorClassMap = (Map<String, Class<?>>) connectorClassMapField.get(null);
// Add our mock class to the map
connectorClassMap.put("TestConnector", MockClassThrowingJsonParseException.class);
// We expect the method to throw JsonParseException
exceptionRule.expect(JsonParseException.class);
exceptionRule.expectMessage("Test JsonParseException");
// Call the method that should throw JsonParseException
MLCommonsClassLoader.initConnector("TestConnector", new Object[]{}, new Class<?>[]{});
}
// Mock class that throws JsonParseException when instantiated
private static class MockClassThrowingJsonParseException {
public MockClassThrowingJsonParseException() throws JsonParseException {
throw new JsonParseException(null, "Test JsonParseException");
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for sharing @dhrubo-os, i added a simpler test case in the latest commit, can you take a look at that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
Signed-off-by: Pavan Yekbote <pybot@amazon.com>
akolarkunnu
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me.
|
Approved the PR. Let's work on the issue you created for validation. Thanks. |
|
Thanks for the review and merge! Can we please add 3.1 backport label as well please? cc: @dhrubo-os @ylwu-amzn |
…execute and MCP (#3988) * improve exception handling Signed-off-by: Pavan Yekbote <pybot@amazon.com> * feat: add test case for jsonparseexception Signed-off-by: Pavan Yekbote <pybot@amazon.com> --------- Signed-off-by: Pavan Yekbote <pybot@amazon.com> Co-authored-by: Dhrubo Saha <dhrubo@amazon.com> (cherry picked from commit 943ef8c)
Description
Throws proper 400 errors for bad requests
Response:
Post this change:
Response:
Post this change:
Related Issues
Resolves #3987
Check List
--signoff.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.