Skip to content

Commit 23829fd

Browse files
committed
RE1-T102 PR#281 fixes
1 parent c1ad916 commit 23829fd

File tree

3 files changed

+207
-6
lines changed

3 files changed

+207
-6
lines changed

Web/Resgrid.Web.Mcp/Controllers/McpController.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.AspNetCore.Mvc;
88
using Microsoft.Extensions.Logging;
99
using ModelContextProtocol.Server;
10+
using Resgrid.Web.Mcp.Infrastructure;
1011

1112
namespace Resgrid.Web.Mcp.Controllers
1213
{
@@ -55,16 +56,18 @@ public async Task<IActionResult> HandleRequest(CancellationToken cancellationTok
5556
message = "Invalid Request: Empty request body"
5657
}
5758
});
58-
}
59+
}
5960

60-
_logger.LogDebug("Received MCP request: {Request}", requestBody);
61+
var redactedRequest = SensitiveDataRedactor.RedactSensitiveFields(requestBody);
62+
_logger.LogDebug("Received MCP request: {Request}", redactedRequest);
6163

62-
// Process the request through the MCP handler
63-
var response = await _mcpHandler.HandleRequestAsync(requestBody, cancellationToken);
64+
// Process the request through the MCP handler
65+
var response = await _mcpHandler.HandleRequestAsync(requestBody, cancellationToken);
6466

65-
_logger.LogDebug("Sending MCP response: {Response}", response);
67+
var redactedResponse = SensitiveDataRedactor.RedactSensitiveFields(response);
68+
_logger.LogDebug("Sending MCP response: {Response}", redactedResponse);
6669

67-
// Return the JSON-RPC response
70+
// Return the JSON-RPC response
6871
return Content(response, "application/json");
6972
}
7073
catch (JsonException ex)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
using System.Text.Json;
3+
using Resgrid.Web.Mcp.Infrastructure;
4+
5+
namespace Resgrid.Web.Mcp.Tests
6+
{
7+
/// <summary>
8+
/// Simple test examples for SensitiveDataRedactor
9+
/// </summary>
10+
public static class SensitiveDataRedactorTests
11+
{
12+
public static void RunTests()
13+
{
14+
Console.WriteLine("Testing SensitiveDataRedactor...\n");
15+
16+
// Test 1: Redact password
17+
var jsonWithPassword = @"{""username"":""john.doe"",""password"":""secret123""}";
18+
var redacted1 = SensitiveDataRedactor.RedactSensitiveFields(jsonWithPassword);
19+
Console.WriteLine($"Original: {jsonWithPassword}");
20+
Console.WriteLine($"Redacted: {redacted1}\n");
21+
22+
// Test 2: Redact token and apikey
23+
var jsonWithToken = @"{""token"":""Bearer abc123"",""apikey"":""xyz789"",""data"":""safe data""}";
24+
var redacted2 = SensitiveDataRedactor.RedactSensitiveFields(jsonWithToken);
25+
Console.WriteLine($"Original: {jsonWithToken}");
26+
Console.WriteLine($"Redacted: {redacted2}\n");
27+
28+
// Test 3: Nested JSON with sensitive fields
29+
var nestedJson = @"{""user"":{""username"":""jane"",""password"":""pass456""},""sessionToken"":""token123""}";
30+
var redacted3 = SensitiveDataRedactor.RedactSensitiveFields(nestedJson);
31+
Console.WriteLine($"Original: {nestedJson}");
32+
Console.WriteLine($"Redacted: {redacted3}\n");
33+
34+
// Test 4: JSON-RPC request with params containing sensitive data
35+
var jsonRpcRequest = @"{""jsonrpc"":""2.0"",""method"":""authenticate"",""params"":{""username"":""admin"",""password"":""admin123""},""id"":1}";
36+
var redacted4 = SensitiveDataRedactor.RedactSensitiveFields(jsonRpcRequest);
37+
Console.WriteLine($"Original: {jsonRpcRequest}");
38+
Console.WriteLine($"Redacted: {redacted4}\n");
39+
40+
// Test 5: Invalid JSON (should return metadata only)
41+
var invalidJson = "not valid json {";
42+
var redacted5 = SensitiveDataRedactor.RedactSensitiveFields(invalidJson);
43+
Console.WriteLine($"Original: {invalidJson}");
44+
Console.WriteLine($"Redacted: {redacted5}\n");
45+
46+
Console.WriteLine("All tests completed!");
47+
}
48+
}
49+
}
50+
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Nodes;
3+
4+
namespace Resgrid.Web.Mcp.Infrastructure
5+
{
6+
/// <summary>
7+
/// Provides functionality to redact sensitive fields from JSON payloads before logging
8+
/// </summary>
9+
public static class SensitiveDataRedactor
10+
{
11+
private static readonly string[] SensitiveFields = new[]
12+
{
13+
"password",
14+
"username",
15+
"token",
16+
"ssn",
17+
"email",
18+
"apikey",
19+
"api_key",
20+
"secret",
21+
"authorization",
22+
"auth",
23+
"credentials",
24+
"credit_card",
25+
"creditcard",
26+
"cvv",
27+
"pin"
28+
};
29+
30+
private const string RedactedValue = "***REDACTED***";
31+
32+
/// <summary>
33+
/// Redacts sensitive fields from a JSON string
34+
/// </summary>
35+
/// <param name="jsonString">The JSON string to redact</param>
36+
/// <returns>A redacted version of the JSON string, or metadata if parsing fails</returns>
37+
public static string RedactSensitiveFields(string jsonString)
38+
{
39+
if (string.IsNullOrWhiteSpace(jsonString))
40+
{
41+
return string.Empty;
42+
}
43+
44+
try
45+
{
46+
var jsonNode = JsonNode.Parse(jsonString);
47+
if (jsonNode == null)
48+
{
49+
return GetMetadataOnly(jsonString);
50+
}
51+
52+
RedactNode(jsonNode);
53+
return jsonNode.ToJsonString(new JsonSerializerOptions { WriteIndented = false });
54+
}
55+
catch (JsonException)
56+
{
57+
// If parsing fails, return only metadata
58+
return GetMetadataOnly(jsonString);
59+
}
60+
}
61+
62+
/// <summary>
63+
/// Extracts only metadata from a JSON string without sensitive data
64+
/// </summary>
65+
/// <param name="jsonString">The JSON string</param>
66+
/// <returns>A string containing only metadata</returns>
67+
private static string GetMetadataOnly(string jsonString)
68+
{
69+
try
70+
{
71+
using var doc = JsonDocument.Parse(jsonString);
72+
var root = doc.RootElement;
73+
74+
var metadata = new
75+
{
76+
method = root.TryGetProperty("method", out var method) ? method.GetString() : null,
77+
id = root.TryGetProperty("id", out var id) ? id.ToString() : null,
78+
jsonrpc = root.TryGetProperty("jsonrpc", out var jsonrpc) ? jsonrpc.GetString() : null,
79+
hasParams = root.TryGetProperty("params", out _),
80+
hasResult = root.TryGetProperty("result", out _),
81+
hasError = root.TryGetProperty("error", out _)
82+
};
83+
84+
return JsonSerializer.Serialize(metadata);
85+
}
86+
catch
87+
{
88+
return $"[Length: {jsonString.Length} bytes]";
89+
}
90+
}
91+
92+
/// <summary>
93+
/// Recursively redacts sensitive fields in a JSON node
94+
/// </summary>
95+
/// <param name="node">The JSON node to redact</param>
96+
private static void RedactNode(JsonNode node)
97+
{
98+
if (node is JsonObject jsonObject)
99+
{
100+
// Create a list of properties to avoid modifying while iterating
101+
var propertiesToProcess = new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<string, JsonNode>>();
102+
foreach (var property in jsonObject)
103+
{
104+
propertiesToProcess.Add(new System.Collections.Generic.KeyValuePair<string, JsonNode>(property.Key, property.Value));
105+
}
106+
107+
foreach (var property in propertiesToProcess)
108+
{
109+
var propertyName = property.Key.ToLowerInvariant();
110+
111+
// Check if this property name matches a sensitive field
112+
var isSensitive = false;
113+
foreach (var sensitiveField in SensitiveFields)
114+
{
115+
if (propertyName.Contains(sensitiveField))
116+
{
117+
isSensitive = true;
118+
break;
119+
}
120+
}
121+
122+
if (isSensitive)
123+
{
124+
jsonObject[property.Key] = RedactedValue;
125+
}
126+
else if (property.Value != null)
127+
{
128+
RedactNode(property.Value);
129+
}
130+
}
131+
}
132+
else if (node is JsonArray jsonArray)
133+
{
134+
for (int i = 0; i < jsonArray.Count; i++)
135+
{
136+
var item = jsonArray[i];
137+
if (item != null)
138+
{
139+
RedactNode(item);
140+
}
141+
}
142+
}
143+
}
144+
}
145+
}
146+
147+
148+

0 commit comments

Comments
 (0)