(t *testing.T)
| 1752 | } |
| 1753 | |
| 1754 | func TestSessionHijackingPrevention(t *testing.T) { |
| 1755 | // This test verifies that sessions bound to a user ID cannot be accessed |
| 1756 | // by a different user (session hijacking prevention). |
| 1757 | ctx := context.Background() |
| 1758 | |
| 1759 | server := NewServer(testImpl, nil) |
| 1760 | streamHandler := NewStreamableHTTPHandler(func(req *http.Request) *Server { return server }, nil) |
| 1761 | |
| 1762 | // Use the bearer token directly as the user ID. This simulates how a real |
| 1763 | // verifier might extract a user ID from a JWT "sub" claim or introspection. |
| 1764 | verifier := func(_ context.Context, token string, _ *http.Request) (*auth.TokenInfo, error) { |
| 1765 | return &auth.TokenInfo{ |
| 1766 | Scopes: []string{"scope"}, |
| 1767 | UserID: token, |
| 1768 | Expiration: time.Date(5000, 1, 2, 3, 4, 5, 0, time.UTC), |
| 1769 | }, nil |
| 1770 | } |
| 1771 | handler := auth.RequireBearerToken(verifier, nil)(streamHandler) |
| 1772 | httpServer := httptest.NewServer(mustNotPanic(t, handler)) |
| 1773 | defer httpServer.Close() |
| 1774 | |
| 1775 | // Helper to send a JSON-RPC request as a given user. |
| 1776 | doRequest := func(msg jsonrpc.Message, sessionID, userID string) *http.Response { |
| 1777 | t.Helper() |
| 1778 | data, _ := jsonrpc2.EncodeMessage(msg) |
| 1779 | req, _ := http.NewRequestWithContext(ctx, http.MethodPost, httpServer.URL, bytes.NewReader(data)) |
| 1780 | req.Header.Set("Content-Type", "application/json") |
| 1781 | req.Header.Set("Accept", "application/json, text/event-stream") |
| 1782 | req.Header.Set("Authorization", "Bearer "+userID) |
| 1783 | if sessionID != "" { |
| 1784 | req.Header.Set(sessionIDHeader, sessionID) |
| 1785 | } |
| 1786 | resp, err := http.DefaultClient.Do(req) |
| 1787 | if err != nil { |
| 1788 | t.Fatalf("request failed: %v", err) |
| 1789 | } |
| 1790 | return resp |
| 1791 | } |
| 1792 | |
| 1793 | // Create a session as user1. |
| 1794 | initReq := &jsonrpc.Request{Method: "initialize", ID: jsonrpc2.Int64ID(1)} |
| 1795 | initReq.Params, _ = json.Marshal(&InitializeParams{ |
| 1796 | ProtocolVersion: protocolVersion20250618, |
| 1797 | ClientInfo: &Implementation{Name: "test", Version: "1.0"}, |
| 1798 | }) |
| 1799 | resp := doRequest(initReq, "", "user1") |
| 1800 | defer resp.Body.Close() |
| 1801 | if resp.StatusCode != http.StatusOK { |
| 1802 | body, _ := io.ReadAll(resp.Body) |
| 1803 | t.Fatalf("initialize failed with status %d: %s", resp.StatusCode, body) |
| 1804 | } |
| 1805 | sessionID := resp.Header.Get(sessionIDHeader) |
| 1806 | if sessionID == "" { |
| 1807 | t.Fatal("no session ID in response") |
| 1808 | } |
| 1809 | |
| 1810 | pingReq := &jsonrpc.Request{Method: "ping", ID: jsonrpc2.Int64ID(2)} |
| 1811 | pingReq.Params, _ = json.Marshal(&PingParams{}) |
nothing calls this directly
no test coverage detected
searching dependent graphs…