fix: avoid saving routing instruction args as state#1364
Conversation
Code Review by Qodo
1. MergeRoutingRules returns incoming reference
|
PR Summary by QodoFix: stop persisting routing instruction arguments into conversation state WalkthroughsDescription• Prevent routing instruction arguments from being stored as conversation state. • Avoid unintended state pollution/leakage across routing/execution loops. Diagramgraph TD
A["RoutingService.InstructLoop"] --> B["HookEmitter (IRoutingHook)"] --> C["RoutingReasoner"] --> D["Executor"] --> E[("ConversationStorage")]
A -. "no longer saves inst.Arguments" .-> F["ConversationStateService"]
subgraph Legend
direction LR
_svc([Service/Component]) ~~~ _db[(Storage)]
end
High-Level AssessmentThe following are alternative approaches to this PR: 1. Whitelist/sanitize args before persisting
2. Persist under a namespaced, ephemeral scope (TTL)
Recommendation: Disabling persistence of routing instruction arguments is a safe, minimal bug fix if those args are transient and not intended as durable state. If some arguments are legitimately needed later, prefer a whitelist/sanitization approach (or a short-lived namespaced scope) rather than saving the raw argument bag into global conversation state. File ChangesBug fix (1)
|
Allow required routing rules to stay available for validation and redirects without exposing those fields for router-generated args.
This reverts commit 58e6ab1.
|
/review |
|
Code review by qodo was updated up to the latest commit 56d1ee7 |
| private static List<RoutingRule> MergeRoutingRules(List<RoutingRule>? existingRules, List<RoutingRule>? incomingRules) | ||
| { | ||
| if (incomingRules == null) | ||
| { | ||
| return []; | ||
| } | ||
|
|
||
| foreach (var incomingRule in incomingRules) | ||
| { | ||
| var existingRule = existingRules?.FirstOrDefault(x => | ||
| string.Equals(x.Field, incomingRule.Field, StringComparison.OrdinalIgnoreCase) && | ||
| string.Equals(x.Type, incomingRule.Type, StringComparison.OrdinalIgnoreCase)); | ||
|
|
||
| if (existingRule != null) | ||
| { | ||
| incomingRule.AllowLlmFill = existingRule.AllowLlmFill; | ||
| } | ||
| } | ||
|
|
||
| return incomingRules; | ||
| } |
There was a problem hiding this comment.
1. mergeroutingrules returns incoming reference 📘 Rule violation ☼ Reliability
MergeRoutingRules mutates the caller-provided incomingRules list (and its items) and then returns the same list instance, which is assigned to record.RoutingRules. This can leak state across components/requests and allow later mutations of the input list to affect persisted agent state.
Agent Prompt
## Issue description
`MergeRoutingRules` modifies and stores the caller-provided `incomingRules` list by reference (`incomingRule.AllowLlmFill = ...` and `return incomingRules;`). This violates the defensive-copy requirement for caller-provided mutable collections and can cause unintended shared-state mutations.
## Issue Context
The assignment `record.RoutingRules = MergeRoutingRules(record.RoutingRules, agent.RoutingRules);` stores whatever `MergeRoutingRules` returns into a long-lived/persisted record. `incomingRules` originates from the caller (`agent.RoutingRules`) and is mutable.
## Fix Focus Areas
- src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs[58-78]
## Implementation notes
- Avoid mutating `incomingRules` directly.
- Create a new `List<RoutingRule>` and clone/copy each incoming rule (e.g., `incomingRule.Clone()`), then apply the merge behavior onto the copies.
- Return the new list and assign that list to `record.RoutingRules`.
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
No description provided.