Skip to content

Features/add conv thumbnail#1297

Merged
Oceania2018 merged 3 commits intoSciSharp:masterfrom
iceljc:features/add-conv-thumbnail
Feb 26, 2026
Merged

Features/add conv thumbnail#1297
Oceania2018 merged 3 commits intoSciSharp:masterfrom
iceljc:features/add-conv-thumbnail

Conversation

@iceljc
Copy link
Collaborator

@iceljc iceljc commented Feb 24, 2026

No description provided.

@qodo-code-review
Copy link

Review Summary by Qodo

Add conversation thumbnail support and dialog ordering functionality
✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add conversation thumbnail support with file management
• Implement dialog ordering (ascending/descending) via ConversationDialogFilter
• Add ConversationFile model and filter classes for file operations
• Enhance SideCarAttribute to handle null arguments with proper type matching
• Extend repository interfaces with conversation file CRUD operations
Diagram
flowchart LR
  A["ConversationFile Model"] --> B["File Management APIs"]
  C["ConversationDialogFilter"] --> D["Dialog Ordering"]
  B --> E["Repository Methods"]
  D --> E
  E --> F["FileRepository Implementation"]
  E --> G["MongoRepository Implementation"]
  F --> H["API Endpoints"]
  G --> H
  H --> I["ConversationViewModel with Thumbnail"]
Loading

Grey Divider

File Changes

1. src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs ✨ Enhancement +1/-2

Add filter parameter to GetDialogHistory method

src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs


2. src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs ✨ Enhancement +3/-1

Add filter parameter to GetDialogs method

src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs


3. src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationFile.cs ✨ Enhancement +7/-0

Create ConversationFile model for metadata

src/Infrastructure/BotSharp.Abstraction/Conversations/Models/ConversationFile.cs


View more (18)
4. src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationDialogFilter.cs ✨ Enhancement +6/-0

Create filter for dialog ordering control

src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationDialogFilter.cs


5. src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFileFilter.cs ✨ Enhancement +6/-0

Create filter for conversation file queries

src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFileFilter.cs


6. src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs ✨ Enhancement +1/-0

Add IsLoadThumbnail flag to filter

src/Infrastructure/BotSharp.Abstraction/Repositories/Filters/ConversationFilter.cs


7. src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs ✨ Enhancement +7/-1

Add conversation file management interface methods

src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs


8. src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs 🐞 Bug fix +24/-2

Improve null argument handling in type matching

src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs


9. src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs ✨ Enhancement +2/-1

Add filter parameter to GetConversationDialogs

src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs


10. src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs ✨ Enhancement +2/-2

Update GetConversationDialogs with filter support

src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs


11. src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs ✨ Enhancement +3/-3

Implement dialog ordering based on filter

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs


12. src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs ✨ Enhancement +2/-2

Pass filter to repository GetConversationDialogs

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs


13. src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs ✨ Enhancement +141/-4

Add dialog ordering and conversation file operations

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs


14. src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs ✨ Enhancement +1/-0

Add conversation files constant definition

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.cs


15. src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs ✨ Enhancement +26/-0

Add thumbnail save and delete endpoints

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs


16. src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs ✨ Enhancement +35/-3

Add thumbnail loading and dialog ordering support

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs


17. src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Request/ConversationFileRequest.cs ✨ Enhancement +9/-0

Create request model for file operations

src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/Request/ConversationFileRequest.cs


18. src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/View/ConversationViewModel.cs ✨ Enhancement +4/-0

Add thumbnail property to conversation view model

src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/View/ConversationViewModel.cs


19. src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationFileDocument.cs ✨ Enhancement +27/-0

Create MongoDB document for conversation files

src/Plugins/BotSharp.Plugin.MongoStorage/Collections/ConversationFileDocument.cs


20. src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs ✨ Enhancement +3/-0

Add ConversationFiles collection to MongoDB context

src/Plugins/BotSharp.Plugin.MongoStorage/MongoDbContext.cs


21. src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs ✨ Enhancement +97/-8

Implement file operations and dialog ordering in MongoDB

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link

qodo-code-review bot commented Feb 24, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (3) 📎 Requirement gaps (0)

Grey Divider


Action required

1. request not null-checked📘 Rule violation ⛯ Reliability
Description
SaveConversationThumbnail dereferences request.Thumbnail without guarding against a null body,
which can throw at this API boundary. This violates the requirement to add null/empty guards and
safe fallbacks for boundary inputs.
Code

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[R121-130]

+    public async Task<bool> SaveConversationThumbnail([FromRoute] string conversationId, [FromBody] ConversationFileRequest request)
+    {
+        var db = _services.GetRequiredService<IBotSharpRepository>();
+        var result = await db.SaveConversationFiles(
+        [
+            new()
+            {
+                ConversationId = conversationId,
+                Thumbnail = request.Thumbnail
+            }
Evidence
The checklist requires null/empty guards at system boundaries; the new controller action reads
request.Thumbnail without checking whether request is null when the request body is
missing/invalid.

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[121-130]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SaveConversationThumbnail` dereferences `request.Thumbnail` without guarding `request` against null, which can throw when the request body is missing/invalid.
## Issue Context
This is an API boundary (`[FromBody]` binding). The compliance rules require explicit null/empty guards and safe fallbacks at system boundaries.
## Fix Focus Areas
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[121-133]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. GetConversationDialogs swallows deserialize errors 📘 Rule violation ✓ Correctness
Description
JSON deserialization failures are caught without logging, which hides corrupt/invalid dialog data
and makes production debugging difficult. This violates the requirement to avoid swallowed
exceptions without logging or actionable context.
Code

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[R94-100]

              {
-                    dialogs = JsonSerializer.Deserialize<List<DialogElement>>(texts, _options) ?? new List<DialogElement>();
+                    dialogs = JsonSerializer.Deserialize<List<DialogElement>>(texts, _options) ?? [];
              }
              catch
              {
-                    dialogs = new List<DialogElement>();
+                    dialogs = [];
+                }
Evidence
The compliance checklist requires robust error handling with meaningful context and disallows
swallowed exceptions without logging; the updated code catches deserialization errors and proceeds
with an empty list without recording the failure.

Rule 3: Generic: Robust Error Handling and Edge Case Management
src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[94-100]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`GetConversationDialogs` catches JSON deserialization exceptions without logging, resulting in silent data corruption/format issues being undetectable.
## Issue Context
The code reads and deserializes a dialogs JSON file; failures currently degrade to `[]` with no telemetry.
## Fix Focus Areas
- src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[93-105]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Sidecar ignores order filter🐞 Bug ✓ Correctness
Description
ConversationService slices dialogs differently for order="desc" vs "asc" and assumes the underlying
dialog list is already ordered accordingly. BotSharpConversationSideCar.GetConversationDialogs
ignores the filter and returns dialogs as-is, so order="desc" can return the wrong subset/order when
sidecar is enabled.
Code

src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs[R57-60]

+    public async Task<List<DialogElement>> GetConversationDialogs(string conversationId, ConversationDialogFilter? filter = null)
  {
      if (!IsValid(conversationId))
      {
Evidence
ConversationService switches from TakeLast(count) to Take(count) when filter.Order=="desc", which
only works if dialogs are already sorted newest-first. FileRepository/MongoRepository explicitly
sort when filter.Order=="desc", but the sidecar implementation does not, creating
inconsistent/incorrect behavior under sidecar mode.

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs[157-194]
src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs[57-67]
src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[76-106]
src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs[95-116]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
When `ConversationDialogFilter.Order == &amp;amp;quot;desc&amp;amp;quot;`, `ConversationService.GetDialogHistory` uses `Take(count)` which assumes dialogs are already sorted newest-first. The sidecar implementation (`BotSharpConversationSideCar.GetConversationDialogs`) ignores the filter and returns dialogs in insertion order, so descending requests can return the wrong subset/order when sidecar is enabled.
### Issue Context
Other repository implementations (FileRepository, MongoRepository) explicitly sort dialogs when `Order == &amp;amp;quot;desc&amp;amp;quot;`, so the sidecar path is now inconsistent.
### Fix Focus Areas
- src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs[57-67]
- src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs[157-194]
### Suggested fix
- In `BotSharpConversationSideCar.GetConversationDialogs`, if `filter?.Order == &amp;amp;quot;desc&amp;amp;quot;`, return `Dialogs.OrderByDescending(d =&amp;amp;gt; d.MetaData?.CreatedTime).ToList()`.
- Optionally harden `ConversationService.GetDialogHistory` by sorting based on `CreatedAt` when a filter is provided, so all storage backends (including sidecar) behave correctly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Thumbnail path traversal 🐞 Bug ⛨ Security
Description
FileRepository.SaveConversationFiles builds filesystem paths using a user-controlled ConversationId
(from the thumbnail API route) without validating or constraining it. A crafted conversationId
containing path separators/.. segments can escape the intended base directory and write/delete files
outside conversation storage.
Code

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[R962-970]

+                var convDir = Path.Combine(baseDir, file.ConversationId);
+                if (!Directory.Exists(convDir))
+                {
+                    Directory.CreateDirectory(convDir);
+                }
+
+                var filesFile = Path.Combine(convDir, CONV_FILES_FILE);
+                var json = JsonSerializer.Serialize(file, _options);
+                await File.WriteAllTextAsync(filesFile, json);
Evidence
The thumbnail endpoint passes the route value conversationId directly to SaveConversationFiles,
which uses Path.Combine(baseDir, file.ConversationId) and writes conv_files.json. There is no
validation to prevent conversationId from containing ../ or absolute paths, so the constructed
path can point outside baseDir.

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[119-141]
src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[951-971]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`SaveConversationFiles` and related methods construct filesystem paths from `ConversationId` without validation. Since `ConversationId` comes from an HTTP route for the thumbnail endpoints, an attacker can use path traversal sequences to escape the intended directory.
### Issue Context
The code uses `Path.Combine(baseDir, conversationId)` and then writes/deletes `conv_files.json`.
### Fix Focus Areas
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[119-141]
- src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[891-1021]
### Suggested fix
- Add a safe path helper in FileRepository (or shared utility) that:
- Rejects IDs containing `Path.DirectorySeparatorChar`, `Path.AltDirectorySeparatorChar`, or `..` segments.
- Computes `fullPath = Path.GetFullPath(Path.Combine(baseDir, conversationId))` and ensures it starts with `Path.GetFullPath(baseDir) + Path.DirectorySeparatorChar`.
- Use that helper for Get/Save/Delete conversation files.
- Optionally add early validation in the controller to reject invalid `conversationId` (defense-in-depth).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Thumbnail ownership bypass 🐞 Bug ⛨ Security
Description
The new thumbnail save/delete endpoints do not verify the conversation exists or belongs to the
caller, unlike other conversation endpoints that scope queries by user for non-admins. Any
authenticated user who knows a conversationId can modify/delete another user's thumbnail metadata.
Code

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[R120-141]

+    [HttpPost("/conversation/{conversationId}/thumbnail")]
+    public async Task<bool> SaveConversationThumbnail([FromRoute] string conversationId, [FromBody] ConversationFileRequest request)
+    {
+        var db = _services.GetRequiredService<IBotSharpRepository>();
+        var result = await db.SaveConversationFiles(
+        [
+            new()
+            {
+                ConversationId = conversationId,
+                Thumbnail = request.Thumbnail
+            }
+        ]);
+        return result;
+    }
+
+    [HttpDelete("/conversation/{conversationId}/thumbnail")]
+    public async Task<bool> DeleteConversationThumbnail([FromRoute] string conversationId)
+    {
+        var db = _services.GetRequiredService<IBotSharpRepository>();
+        var result = await db.DeleteConversationFiles([conversationId]);
+        return result;
+    }
Evidence
GetConversation/GetConversations explicitly scope non-admin access by setting
ConversationFilter.UserId to the current user before fetching. The new thumbnail endpoints directly
write/delete by conversationId with no analogous check, creating an authorization bypass relative to
the existing access pattern.

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[50-61]
src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[172-180]
src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[119-141]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Thumbnail modification endpoints bypass the ownership filtering used by other conversation endpoints. This allows unauthorized modification if a user can obtain another conversation&amp;amp;#x27;s ID.
### Issue Context
`GetConversation` and `GetConversations` scope non-admin queries by `ConversationFilter.UserId`. Thumbnail endpoints currently call the repository directly without checking ownership.
### Fix Focus Areas
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[119-141]
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[50-61]
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[172-180]
### Suggested fix
- In `SaveConversationThumbnail` and `DeleteConversationThumbnail`:
- Call `userService.IsAdminUser(_user.Id)`.
- If not admin, verify the conversation exists and has `UserId == _user.Id` (e.g., via `IConversationService.GetConversations(new ConversationFilter { Id = conversationId, UserId = _user.Id })`).
- Return 404/403 when not found/unauthorized (prefer IActionResult over bool to represent errors).
- Only after authorization passes, call `SaveConversationFiles/DeleteConversationFiles`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

6. Order comparison is case-sensitive 📘 Rule violation ✓ Correctness
Description
The new ordering behavior relies on exact string matches ("desc"), so inputs like DESC/Desc
will silently behave differently. This creates brittle contract behavior and violates robust
matching expectations for API/configuration inputs.
Code

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs[193]

+        return filter?.Order == "desc" ? dialogs.Take(count).ToList() : dialogs.TakeLast(count).ToList();
Evidence
The checklist calls for robust matching; the PR introduces user-controlled order which is compared
using case-sensitive equality, making the contract unintentionally case-sensitive.

src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs[193-193]
src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[98-108]
Best Practice: Learned patterns

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Ordering behavior is controlled by a free-form string and compared case-sensitively to `&amp;amp;quot;desc&amp;amp;quot;`, making the API contract brittle and unintentionally case-sensitive.
## Issue Context
The query parameter `order` is passed into `ConversationDialogFilter.Order` and later evaluated using `== &amp;amp;quot;desc&amp;amp;quot;`.
## Fix Focus Areas
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[98-108]
- src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs[193-193]
- src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[102-105]
- src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs[111-116]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Thumbnail lookup inefficiency 🐞 Bug ➹ Performance
Description
GetConversations loads thumbnails into a list, then for each conversation performs a linear search
(FirstOrDefault) to find its thumbnail. This is unnecessary repeated scanning and can be avoided by
pre-indexing files by ConversationId.
Code

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[R71-88]

+        var files = new List<ConversationFile>();
+        if (filter.IsLoadThumbnail)
+        {
+            var db = _services.GetRequiredService<IBotSharpRepository>();
+            files = await db.GetConversationFiles(new ConversationFileFilter
+            {
+                ConversationIds = list.Select(x => x.Id)
+            });
+        }
+
      foreach (var item in list)
      {
          user = users.FirstOrDefault(x => x.Id == item.User.Id);
          item.User = UserViewModel.FromUser(user);
          var agent = agents.FirstOrDefault(x => x.Id == item.AgentId);
          item.AgentName = agent?.Name ?? "Unkown";
+            item.Thumbnail = !files.IsNullOrEmpty() ? files.FirstOrDefault(x => x.ConversationId == item.Id)?.Thumbnail : null;
      }
Evidence
The controller loops over the conversation list and calls FirstOrDefault on the files list for each
item, which scales poorly as page size grows. A dictionary lookup would be O(1) per item.

src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[71-88]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The current thumbnail mapping does a linear search over `files` for each conversation item.
### Issue Context
This runs once per page in `GET /conversations` when `IsLoadThumbnail` is enabled.
### Fix Focus Areas
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[71-88]
### Suggested fix
- Build `var thumbnailByConvId = files.ToDictionary(f =&amp;amp;gt; f.ConversationId, f =&amp;amp;gt; f.Thumbnail);`
- Inside the loop, set `item.Thumbnail = thumbnailByConvId.TryGetValue(item.Id, out var t) ? t : null;`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines 121 to 130
public async Task<bool> SaveConversationThumbnail([FromRoute] string conversationId, [FromBody] ConversationFileRequest request)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var result = await db.SaveConversationFiles(
[
new()
{
ConversationId = conversationId,
Thumbnail = request.Thumbnail
}

Choose a reason for hiding this comment

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

Action required

1. request not null-checked 📘 Rule violation ⛯ Reliability

SaveConversationThumbnail dereferences request.Thumbnail without guarding against a null body,
which can throw at this API boundary. This violates the requirement to add null/empty guards and
safe fallbacks for boundary inputs.
Agent Prompt
## Issue description
`SaveConversationThumbnail` dereferences `request.Thumbnail` without guarding `request` against null, which can throw when the request body is missing/invalid.

## Issue Context
This is an API boundary (`[FromBody]` binding). The compliance rules require explicit null/empty guards and safe fallbacks at system boundaries.

## Fix Focus Areas
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[121-133]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 94 to +100
{
dialogs = JsonSerializer.Deserialize<List<DialogElement>>(texts, _options) ?? new List<DialogElement>();
dialogs = JsonSerializer.Deserialize<List<DialogElement>>(texts, _options) ?? [];
}
catch
{
dialogs = new List<DialogElement>();
dialogs = [];
}

Choose a reason for hiding this comment

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

Action required

2. getconversationdialogs swallows deserialize errors 📘 Rule violation ✧ Quality

JSON deserialization failures are caught without logging, which hides corrupt/invalid dialog data
and makes production debugging difficult. This violates the requirement to avoid swallowed
exceptions without logging or actionable context.
Agent Prompt
## Issue description
`GetConversationDialogs` catches JSON deserialization exceptions without logging, resulting in silent data corruption/format issues being undetectable.

## Issue Context
The code reads and deserializes a dialogs JSON file; failures currently degrade to `[]` with no telemetry.

## Fix Focus Areas
- src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[93-105]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +57 to 60
public async Task<List<DialogElement>> GetConversationDialogs(string conversationId, ConversationDialogFilter? filter = null)
{
if (!IsValid(conversationId))
{

Choose a reason for hiding this comment

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

Action required

3. Sidecar ignores order filter 🐞 Bug ✓ Correctness

ConversationService slices dialogs differently for order="desc" vs "asc" and assumes the underlying
dialog list is already ordered accordingly. BotSharpConversationSideCar.GetConversationDialogs
ignores the filter and returns dialogs as-is, so order="desc" can return the wrong subset/order when
sidecar is enabled.
Agent Prompt
### Issue description
When `ConversationDialogFilter.Order == "desc"`, `ConversationService.GetDialogHistory` uses `Take(count)` which assumes dialogs are already sorted newest-first. The sidecar implementation (`BotSharpConversationSideCar.GetConversationDialogs`) ignores the filter and returns dialogs in insertion order, so descending requests can return the wrong subset/order when sidecar is enabled.

### Issue Context
Other repository implementations (FileRepository, MongoRepository) explicitly sort dialogs when `Order == "desc"`, so the sidecar path is now inconsistent.

### Fix Focus Areas
- src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs[57-67]
- src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs[157-194]

### Suggested fix
- In `BotSharpConversationSideCar.GetConversationDialogs`, if `filter?.Order == "desc"`, return `Dialogs.OrderByDescending(d => d.MetaData?.CreatedTime).ToList()`.
- Optionally harden `ConversationService.GetDialogHistory` by sorting based on `CreatedAt` when a filter is provided, so all storage backends (including sidecar) behave correctly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 962 to 970
var convDir = Path.Combine(baseDir, file.ConversationId);
if (!Directory.Exists(convDir))
{
Directory.CreateDirectory(convDir);
}

var filesFile = Path.Combine(convDir, CONV_FILES_FILE);
var json = JsonSerializer.Serialize(file, _options);
await File.WriteAllTextAsync(filesFile, json);

Choose a reason for hiding this comment

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

Action required

4. Thumbnail path traversal 🐞 Bug ⛨ Security

FileRepository.SaveConversationFiles builds filesystem paths using a user-controlled ConversationId
(from the thumbnail API route) without validating or constraining it. A crafted conversationId
containing path separators/.. segments can escape the intended base directory and write/delete files
outside conversation storage.
Agent Prompt
### Issue description
`SaveConversationFiles` and related methods construct filesystem paths from `ConversationId` without validation. Since `ConversationId` comes from an HTTP route for the thumbnail endpoints, an attacker can use path traversal sequences to escape the intended directory.

### Issue Context
The code uses `Path.Combine(baseDir, conversationId)` and then writes/deletes `conv_files.json`.

### Fix Focus Areas
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[119-141]
- src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs[891-1021]

### Suggested fix
- Add a safe path helper in FileRepository (or shared utility) that:
  - Rejects IDs containing `Path.DirectorySeparatorChar`, `Path.AltDirectorySeparatorChar`, or `..` segments.
  - Computes `fullPath = Path.GetFullPath(Path.Combine(baseDir, conversationId))` and ensures it starts with `Path.GetFullPath(baseDir) + Path.DirectorySeparatorChar`.
- Use that helper for Get/Save/Delete conversation files.
- Optionally add early validation in the controller to reject invalid `conversationId` (defense-in-depth).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 120 to 141
[HttpPost("/conversation/{conversationId}/thumbnail")]
public async Task<bool> SaveConversationThumbnail([FromRoute] string conversationId, [FromBody] ConversationFileRequest request)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var result = await db.SaveConversationFiles(
[
new()
{
ConversationId = conversationId,
Thumbnail = request.Thumbnail
}
]);
return result;
}

[HttpDelete("/conversation/{conversationId}/thumbnail")]
public async Task<bool> DeleteConversationThumbnail([FromRoute] string conversationId)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
var result = await db.DeleteConversationFiles([conversationId]);
return result;
}

Choose a reason for hiding this comment

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

Action required

5. Thumbnail ownership bypass 🐞 Bug ⛨ Security

The new thumbnail save/delete endpoints do not verify the conversation exists or belongs to the
caller, unlike other conversation endpoints that scope queries by user for non-admins. Any
authenticated user who knows a conversationId can modify/delete another user's thumbnail metadata.
Agent Prompt
### Issue description
Thumbnail modification endpoints bypass the ownership filtering used by other conversation endpoints. This allows unauthorized modification if a user can obtain another conversation's ID.

### Issue Context
`GetConversation` and `GetConversations` scope non-admin queries by `ConversationFilter.UserId`. Thumbnail endpoints currently call the repository directly without checking ownership.

### Fix Focus Areas
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.File.cs[119-141]
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[50-61]
- src/Infrastructure/BotSharp.OpenAPI/Controllers/Conversation/ConversationController.cs[172-180]

### Suggested fix
- In `SaveConversationThumbnail` and `DeleteConversationThumbnail`:
  - Call `userService.IsAdminUser(_user.Id)`.
  - If not admin, verify the conversation exists and has `UserId == _user.Id` (e.g., via `IConversationService.GetConversations(new ConversationFilter { Id = conversationId, UserId = _user.Id })`).
  - Return 404/403 when not found/unauthorized (prefer IActionResult over bool to represent errors).
- Only after authorization passes, call `SaveConversationFiles/DeleteConversationFiles`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@Oceania2018 Oceania2018 merged commit 2d9f0df into SciSharp:master Feb 26, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants