Summary
ChatController accepts a previousResponseId from the client and forwards it directly to Azure OpenAI without verifying that the ID was issued to the currently authenticated user. Since all users share the same Azure OpenAI deployment and API key, any authenticated user who learns another user's response ID can continue (and read back) that user's conversation.
Affected Code
EssentialCSharp.Web/Controllers/ChatController.cs:
var previousResponseId = string.IsNullOrWhiteSpace(request.PreviousResponseId)
? null
: request.PreviousResponseId.Trim(); // ← taken from client, no ownership check
var (response, responseId) = await _AiChatService.GetChatCompletion(
prompt: request.Message,
previousResponseId: previousResponseId, // ← forwarded to OpenAI as-is
...);
AIChatService.cs passes it directly to ResponseCreationOptions.PreviousResponseId.
Attack Scenario
- Attacker authenticates and sends a normal chat request.
- Attacker learns a victim's
responseId (e.g., via shared browser, logs, XSS, or by observing network traffic).
- Attacker calls
POST /api/chat/message with PreviousResponseId set to the victim's ID.
- Azure OpenAI resumes the victim's conversation context, potentially exposing prior conversation content in the new response.
Risk
OWASP AI Agent Security — §3 Memory & Context Security / §8 Data Protection & Privacy
- Azure OpenAI response IDs are opaque but not bound to a specific API caller identity; they're valid for any caller using the same deployment.
- The lack of server-side binding means there's no isolation between user conversation sessions at the application layer.
- The
lastResponseId is also persisted in localStorage client-side, widening exposure.
Recommended Mitigation
Store issued response IDs server-side keyed to the authenticated user (e.g., in the ASP.NET session or a cache), and validate on each request:
// On chat completion, store the response ID against the user
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)!;
_cache.Set($"chat:lastResponseId:{userId}", responseId, TimeSpan.FromHours(24));
// On next request, validate the supplied ID matches what's stored for this user
if (previousResponseId != null)
{
var storedId = _cache.Get<string>($"chat:lastResponseId:{userId}");
if (storedId != previousResponseId)
return BadRequest(new { error = "Invalid conversation context." });
}
Alternatively, enforce that the server always issues and tracks the response ID server-side, never trusting the client-supplied value.
Summary
ChatControlleraccepts apreviousResponseIdfrom the client and forwards it directly to Azure OpenAI without verifying that the ID was issued to the currently authenticated user. Since all users share the same Azure OpenAI deployment and API key, any authenticated user who learns another user's response ID can continue (and read back) that user's conversation.Affected Code
EssentialCSharp.Web/Controllers/ChatController.cs:AIChatService.cspasses it directly toResponseCreationOptions.PreviousResponseId.Attack Scenario
responseId(e.g., via shared browser, logs, XSS, or by observing network traffic).POST /api/chat/messagewithPreviousResponseIdset to the victim's ID.Risk
OWASP AI Agent Security — §3 Memory & Context Security / §8 Data Protection & Privacy
lastResponseIdis also persisted inlocalStorageclient-side, widening exposure.Recommended Mitigation
Store issued response IDs server-side keyed to the authenticated user (e.g., in the ASP.NET session or a cache), and validate on each request:
Alternatively, enforce that the server always issues and tracks the response ID server-side, never trusting the client-supplied value.