first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:52:23 +03:00
commit 880f412e2c
2662 changed files with 866266 additions and 0 deletions

View File

@@ -0,0 +1,655 @@
package mcptests
import (
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/maximhq/bifrost/core/schemas"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// =============================================================================
// PATH TRAVERSAL SECURITY TESTS
// =============================================================================
func TestReadToolFile_PathTraversalAttacks(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
// Setup code mode client
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
pathTraversalTests := []struct {
name string
fileName string
expectError bool
errorMessage string
}{
{
name: "basic_path_traversal_parent",
fileName: "../../../etc/passwd.pyi",
expectError: true,
errorMessage: "No server found matching",
},
{
name: "path_traversal_in_server_name",
fileName: "servers/../../secrets.pyi",
expectError: true,
errorMessage: "No server found matching",
},
{
name: "double_dot_in_tool_name",
fileName: "servers/validserver/../../../etc.pyi",
expectError: true,
errorMessage: "No server found matching",
},
{
name: "encoded_path_traversal",
fileName: "servers/..%2F..%2F..%2Fetc%2Fpasswd.pyi",
expectError: true,
errorMessage: "No server found matching",
},
{
name: "path_with_multiple_slashes",
fileName: "servers///..//..//etc//passwd.pyi",
expectError: true,
errorMessage: "No server found matching",
},
{
name: "absolute_path",
fileName: "/etc/passwd.pyi",
expectError: true,
errorMessage: "No server found matching",
},
}
for _, tc := range pathTraversalTests {
t.Run(tc.name, func(t *testing.T) {
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-" + tc.name),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("readToolFile"),
Arguments: `{"fileName": "` + tc.fileName + `"}`,
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
if tc.expectError {
// Should either return error or error message in result
if bifrostErr != nil {
assert.Contains(t, bifrostErr.Error.Message, tc.errorMessage)
} else if result != nil && result.Content != nil && result.Content.ContentStr != nil {
assert.Contains(t, *result.Content.ContentStr, tc.errorMessage)
} else {
t.Errorf("Expected error but got success")
}
}
})
}
}
func TestReadToolFile_InvalidToolNames(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
invalidNameTests := []struct {
name string
fileName string
}{
{"slash_in_tool_name", "servers/validserver/tool/with/slashes.pyi"},
{"dot_dot_in_tool_name", "servers/validserver/tool..name.pyi"},
{"special_chars", "servers/validserver/tool<>:\"|?*.pyi"},
{"null_byte", "servers/validserver/tool\x00name.pyi"},
{"backslash", "servers/validserver/tool\\name.pyi"},
}
for _, tc := range invalidNameTests {
t.Run(tc.name, func(t *testing.T) {
argsMap := map[string]string{"fileName": tc.fileName}
argsJSON, err := json.Marshal(argsMap)
if err != nil {
t.Fatalf("failed to marshal arguments: %v", err)
}
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-" + tc.name),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("readToolFile"),
Arguments: string(argsJSON),
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
// Should return error or error message
if bifrostErr == nil && result != nil && result.Content != nil && result.Content.ContentStr != nil {
// Verify error message indicates tool not found
assert.Contains(t, *result.Content.ContentStr, "No server found matching")
}
})
}
}
// =============================================================================
// CODE INJECTION SECURITY TESTS
// =============================================================================
func TestExecuteToolCode_CodeInjectionAttempts(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
injectionTests := []struct {
name string
code string
shouldFail bool
description string
}{
{
name: "import_os",
code: "import os\nresult = os.system('ls')",
shouldFail: true,
description: "Attempt to import os module",
},
{
name: "load_module",
code: "load('foo.star', 'bar')\nresult = bar()",
shouldFail: true,
description: "Attempt to load external module",
},
{
name: "eval_usage",
code: "result = 'done'",
shouldFail: false, // Simple assignment should succeed
description: "Simple code execution",
},
{
name: "infinite_loop",
code: "def loop():\n while True:\n pass\nloop()",
shouldFail: true,
description: "Infinite loop should timeout",
},
{
name: "prototype_pollution",
code: "result = {'polluted': 'yes'}",
shouldFail: false, // Simple dict creation should succeed
description: "Dict creation (Starlark has no prototypes)",
},
}
for _, tc := range injectionTests {
t.Run(tc.name, func(t *testing.T) {
codeJSON, _ := json.Marshal(tc.code)
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-" + tc.name),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("executeToolCode"),
Arguments: `{"code": ` + string(codeJSON) + `}`,
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
if tc.shouldFail {
// Determine if execution actually failed
executionFailed := false
var failureReason string
if bifrostErr != nil {
executionFailed = true
failureReason = fmt.Sprintf("bifrostErr: %v", bifrostErr)
} else if result != nil && result.Content != nil && result.Content.ContentStr != nil {
returnValue, hasError, errorMsg := ParseCodeModeResponse(t, *result.Content.ContentStr)
if hasError {
executionFailed = true
failureReason = fmt.Sprintf("ParseCodeModeResponse error: %s", errorMsg)
} else if returnValue != nil {
if returnObj, ok := returnValue.(map[string]interface{}); ok {
if errorField, ok := returnObj["error"]; ok {
executionFailed = true
failureReason = fmt.Sprintf("return value error field: %v", errorField)
}
}
}
}
if !executionFailed {
t.Errorf("%s (%s): expected execution to fail but it succeeded", tc.name, tc.description)
return
}
t.Logf("%s: execution failed as expected - %s", tc.name, failureReason)
} else {
// shouldFail == false: assert execution succeeded without errors
if bifrostErr != nil {
t.Errorf("%s (%s): unexpected bifrostErr: %v", tc.name, tc.description, bifrostErr)
return
}
if result == nil || result.Content == nil || result.Content.ContentStr == nil {
t.Errorf("%s (%s): expected result with content but got nil", tc.name, tc.description)
return
}
_, hasError, errorMsg := ParseCodeModeResponse(t, *result.Content.ContentStr)
if hasError {
t.Errorf("%s (%s): unexpected error in response: %s", tc.name, tc.description, errorMsg)
return
}
t.Logf("%s: execution succeeded as expected", tc.name)
}
})
}
}
// =============================================================================
// INPUT VALIDATION SECURITY TESTS
// =============================================================================
func TestListToolFiles_InputValidation(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
// Test listToolFiles with no parameters (should succeed)
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-list"),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("listToolFiles"),
Arguments: `{}`,
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
require.Nil(t, bifrostErr, "listToolFiles should succeed")
require.NotNil(t, result)
}
func TestReadToolFile_EmptyFileName(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
testCases := []struct {
name string
arguments string
}{
{"empty_string", `{"fileName": ""}`},
{"only_spaces", `{"fileName": " "}`},
{"missing_field", `{}`},
{"null_value", `{"fileName": null}`},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-" + tc.name),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("readToolFile"),
Arguments: tc.arguments,
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
// Should return error or error message
if bifrostErr == nil && result != nil {
// Check if error is in result content
if result.Content != nil && result.Content.ContentStr != nil {
content := *result.Content.ContentStr
// Should contain some error indication
assert.True(t, strings.Contains(content, "error") ||
strings.Contains(content, "required") ||
strings.Contains(content, "invalid") ||
strings.Contains(content, "found") || // Updated to just "found" not "not found"
strings.Contains(content, "Available virtual files"), // Also accept list of available files
"Should return error message, got: %s", content)
}
}
})
}
}
func TestExecuteToolCode_EmptyCodeSecurity(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
testCases := []struct {
name string
arguments string
}{
{"empty_string", `{"code": ""}`},
{"only_spaces", `{"code": " "}`},
{"only_newlines", `{"code": "\n\n\n"}`},
{"missing_field", `{}`},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-" + tc.name),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("executeToolCode"),
Arguments: tc.arguments,
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
// Should return error
if bifrostErr == nil && result != nil && result.Content != nil && result.Content.ContentStr != nil {
returnValue, hasError, _ := ParseCodeModeResponse(t, *result.Content.ContentStr)
if !hasError {
// Check if result is empty or null
assert.True(t, returnValue == nil || returnValue == "",
"Empty code should return empty or null result")
}
}
})
}
}
// =============================================================================
// UNICODE AND ENCODING TESTS
// =============================================================================
func TestExecuteToolCode_UnicodeInCode(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
unicodeCode := `result = "Hello 🌍"`
codeJSON, _ := json.Marshal(unicodeCode)
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-unicode"),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("executeToolCode"),
Arguments: `{"code": ` + string(codeJSON) + `}`,
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
require.Nil(t, bifrostErr)
require.NotNil(t, result)
if result.Content != nil && result.Content.ContentStr != nil {
returnValue, hasError, errorMsg := ParseCodeModeResponse(t, *result.Content.ContentStr)
require.False(t, hasError, "should not have execution error: %s", errorMsg)
// Should handle unicode correctly
resultStr := fmt.Sprintf("%v", returnValue)
assert.Contains(t, resultStr, "Hello 🌍")
}
}
// =============================================================================
// MALFORMED JSON TESTS
// =============================================================================
func TestExecuteToolCode_MalformedJSON(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
malformedTests := []struct {
name string
arguments string
}{
{"missing_closing_brace", `{"code": "return 42"`},
{"missing_quotes", `{code: "return 42"}`},
{"trailing_comma", `{"code": "return 42",}`},
{"unescaped_newline", `{"code": "return
42"}`},
{"invalid_escape", `{"code": "return \x"}`},
}
for _, tc := range malformedTests {
t.Run(tc.name, func(t *testing.T) {
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-" + tc.name),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("executeToolCode"),
Arguments: tc.arguments,
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
// Should return error
if bifrostErr != nil {
assert.NotEmpty(t, bifrostErr.Error.Message)
} else if result != nil && result.Content != nil && result.Content.ContentStr != nil {
// Error might be in result content
content := *result.Content.ContentStr
// Should indicate some kind of error
assert.True(t, strings.Contains(content, "error") ||
strings.Contains(content, "invalid") ||
strings.Contains(content, "failed"),
"Should indicate error, got: %s", content)
}
})
}
}
// =============================================================================
// LINE NUMBER BOUNDARY TESTS
// =============================================================================
func TestReadToolFile_LineNumberBoundaries(t *testing.T) {
t.Parallel()
config := GetTestConfig(t)
if config.HTTPServerURL == "" {
t.Skip("MCP_HTTP_URL not set")
}
codeModeClient := GetSampleCodeModeClientConfig(t, config.HTTPServerURL)
manager := setupMCPManager(t, codeModeClient)
bifrost := setupBifrost(t)
bifrost.SetMCPManager(manager)
ctx := createTestContext()
// First, list available files to get a real server name
listCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-list"),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("listToolFiles"),
Arguments: `{}`,
},
}
listResult, _ := bifrost.ExecuteChatMCPTool(ctx, &listCall)
if listResult == nil || listResult.Content == nil || listResult.Content.ContentStr == nil {
t.Skip("No code mode servers available")
}
// Parse the list result to get a real file name
content := *listResult.Content.ContentStr
if !strings.Contains(content, ".pyi") {
t.Skip("No .pyi files found")
}
// Extract first .pyi file name
lines := strings.Split(content, "\n")
var firstFile string
for _, line := range lines {
if strings.Contains(line, ".pyi") {
// Extract just the filename
parts := strings.Fields(line)
for _, part := range parts {
if strings.HasSuffix(part, ".pyi") {
firstFile = strings.Trim(part, "[]\"',")
break
}
}
if firstFile != "" {
break
}
}
}
if firstFile == "" {
t.Skip("Could not extract file name from list")
}
// The readToolFile tool handles invalid line numbers gracefully by returning content
// rather than returning explicit error messages. Test that the tool doesn't crash
// and returns some content for edge cases.
boundaryTests := []struct {
name string
startLine int
endLine int
description string
}{
{
name: "start_line_zero",
startLine: 0,
endLine: 5,
description: "Tool should handle startLine=0 gracefully",
},
{
name: "start_line_negative",
startLine: -1,
endLine: 5,
description: "Tool should handle negative startLine gracefully",
},
{
name: "end_line_before_start",
startLine: 10,
endLine: 5,
description: "Tool should handle endLine < startLine gracefully",
},
{
name: "very_large_line_number",
startLine: 1,
endLine: 999999,
description: "Tool should handle very large endLine gracefully",
},
}
for _, tc := range boundaryTests {
t.Run(tc.name, func(t *testing.T) {
args := map[string]interface{}{
"fileName": firstFile,
"startLine": tc.startLine,
"endLine": tc.endLine,
}
argsJSON, _ := json.Marshal(args)
toolCall := schemas.ChatAssistantMessageToolCall{
ID: schemas.Ptr("call-" + tc.name),
Type: schemas.Ptr("function"),
Function: schemas.ChatAssistantMessageToolCallFunction{
Name: schemas.Ptr("readToolFile"),
Arguments: string(argsJSON),
},
}
result, bifrostErr := bifrost.ExecuteChatMCPTool(ctx, &toolCall)
// The tool should not crash and should return a result
// It handles invalid line numbers gracefully by returning content
if bifrostErr != nil {
t.Logf("%s: Got bifrost error (acceptable): %v", tc.description, bifrostErr)
} else {
require.NotNil(t, result, "%s: result should not be nil", tc.description)
require.NotNil(t, result.Content, "%s: result.Content should not be nil", tc.description)
require.NotNil(t, result.Content.ContentStr, "%s: result.Content.ContentStr should not be nil", tc.description)
// Just verify we got some content back (tool handles gracefully)
t.Logf("%s: Got response with %d characters", tc.description, len(*result.Content.ContentStr))
}
})
}
}