Skip to content

Commit 83ce6a9

Browse files
authored
Sanitize user input in log statements for durable agent samples. (#4656)
1 parent 50fdcba commit 83ce6a9

File tree

2 files changed

+34
-9
lines changed

2 files changed

+34
-9
lines changed

dotnet/samples/04-hosting/DurableAgents/AzureFunctions/06_LongRunningTools/Tools.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal sealed class Tools(ILogger<Tools> logger)
1717
[Description("Starts a content generation workflow and returns the instance ID for tracking.")]
1818
public string StartContentGenerationWorkflow([Description("The topic for content generation")] string topic)
1919
{
20-
this._logger.LogInformation("Starting content generation workflow for topic: {Topic}", topic);
20+
this._logger.LogInformation("Starting content generation workflow for topic: {Topic}", SanitizeLogValue(topic));
2121

2222
const int MaxReviewAttempts = 3;
2323
const float ApprovalTimeoutHours = 72;
@@ -34,7 +34,7 @@ internal sealed class Tools(ILogger<Tools> logger)
3434

3535
this._logger.LogInformation(
3636
"Content generation workflow scheduled to be started for topic '{Topic}' with instance ID: {InstanceId}",
37-
topic,
37+
SanitizeLogValue(topic),
3838
instanceId);
3939

4040
return $"Workflow started with instance ID: {instanceId}";
@@ -45,7 +45,7 @@ public async Task<object> GetWorkflowStatusAsync(
4545
[Description("The instance ID of the workflow to check")] string instanceId,
4646
[Description("Whether to include detailed information")] bool includeDetails = true)
4747
{
48-
this._logger.LogInformation("Getting status for workflow instance: {InstanceId}", instanceId);
48+
this._logger.LogInformation("Getting status for workflow instance: {InstanceId}", SanitizeLogValue(instanceId));
4949

5050
// Get the current agent context using the session-static property
5151
OrchestrationMetadata? status = await DurableAgentContext.Current.GetOrchestrationStatusAsync(
@@ -54,7 +54,7 @@ public async Task<object> GetWorkflowStatusAsync(
5454

5555
if (status is null)
5656
{
57-
this._logger.LogInformation("Workflow instance '{InstanceId}' not found.", instanceId);
57+
this._logger.LogInformation("Workflow instance '{InstanceId}' not found.", SanitizeLogValue(instanceId));
5858
return new
5959
{
6060
instanceId,
@@ -78,7 +78,16 @@ public async Task SubmitHumanApprovalAsync(
7878
[Description("The instance ID of the workflow to submit feedback for")] string instanceId,
7979
[Description("Feedback to submit")] HumanApprovalResponse feedback)
8080
{
81-
this._logger.LogInformation("Submitting human approval for workflow instance: {InstanceId}", instanceId);
81+
this._logger.LogInformation("Submitting human approval for workflow instance: {InstanceId}", SanitizeLogValue(instanceId));
8282
await DurableAgentContext.Current.RaiseOrchestrationEventAsync(instanceId, "HumanApproval", feedback);
8383
}
84+
85+
/// <summary>
86+
/// Sanitizes a user-provided value for safe inclusion in log entries
87+
/// by removing control characters that could be used for log forging.
88+
/// </summary>
89+
private static string SanitizeLogValue(string value) =>
90+
value
91+
.Replace("\r", string.Empty, StringComparison.Ordinal)
92+
.Replace("\n", string.Empty, StringComparison.Ordinal);
8493
}

dotnet/samples/04-hosting/DurableAgents/AzureFunctions/08_ReliableStreaming/FunctionTriggers.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ public async Task<IActionResult> StreamAsync(
157157

158158
this._logger.LogInformation(
159159
"Resuming stream for conversation {ConversationId} from cursor: {Cursor}",
160-
conversationId,
161-
cursor ?? "(beginning)");
160+
SanitizeLogValue(conversationId),
161+
SanitizeLogValue(cursor) ?? "(beginning)");
162162

163163
// Check Accept header to determine response format
164164
// text/plain = raw text output (ideal for terminals)
@@ -205,7 +205,7 @@ private async Task<IActionResult> StreamToClientAsync(
205205
{
206206
if (chunk.Error != null)
207207
{
208-
this._logger.LogWarning("Stream error for conversation {ConversationId}: {Error}", conversationId, chunk.Error);
208+
this._logger.LogWarning("Stream error for conversation {ConversationId}: {Error}", SanitizeLogValue(conversationId), chunk.Error);
209209
await WriteErrorAsync(httpContext.Response, chunk.Error, useSseFormat, cancellationToken);
210210
break;
211211
}
@@ -224,7 +224,7 @@ private async Task<IActionResult> StreamToClientAsync(
224224
}
225225
catch (OperationCanceledException)
226226
{
227-
this._logger.LogInformation("Client disconnected from stream {ConversationId}", conversationId);
227+
this._logger.LogInformation("Client disconnected from stream {ConversationId}", SanitizeLogValue(conversationId));
228228
}
229229

230230
return new EmptyResult();
@@ -316,4 +316,20 @@ private static async Task WriteSSEEventAsync(
316316

317317
await response.WriteAsync(sb.ToString());
318318
}
319+
320+
/// <summary>
321+
/// Sanitizes a user-provided value for safe inclusion in log entries
322+
/// by removing control characters that could be used for log forging.
323+
/// </summary>
324+
private static string? SanitizeLogValue(string? value)
325+
{
326+
if (value is null)
327+
{
328+
return null;
329+
}
330+
331+
return value
332+
.Replace("\r", string.Empty, StringComparison.Ordinal)
333+
.Replace("\n", string.Empty, StringComparison.Ordinal);
334+
}
319335
}

0 commit comments

Comments
 (0)