diff --git a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DiagnosticsPathGuards.cs b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DiagnosticsPathGuards.cs
index f3b6e3ad..23388e25 100644
--- a/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DiagnosticsPathGuards.cs
+++ b/src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DiagnosticsPathGuards.cs
@@ -106,6 +106,10 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
normalizedDir = Path.GetFullPath(expanded);
}
+ // Symlink / junction / reparse point detection — fail-closed
+ if (IsSymlinkOrReparsePoint(normalizedDir))
+ return false;
+
// C:\Users block always applies (even in unrestricted mode)
if (normalizedDir.StartsWith(BlockedUsersPrefix, StringComparison.OrdinalIgnoreCase) &&
(normalizedDir.Length == BlockedUsersPrefix.Length ||
@@ -152,5 +156,45 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
return false;
}
}
+
+ ///
+ /// Checks whether any component of the path (directories or file) is a symlink,
+ /// junction, or other reparse point. Returns true if a reparse point is detected.
+ /// Fail-closed: returns true on any error.
+ ///
+ private static bool IsSymlinkOrReparsePoint(string path)
+ {
+ try
+ {
+ // Walk up the directory tree checking each component
+ var current = path;
+ while (!string.IsNullOrEmpty(current))
+ {
+ var dirInfo = new DirectoryInfo(current);
+ if (dirInfo.Exists && dirInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
+ return true;
+
+ var parent = Path.GetDirectoryName(current);
+ if (parent == current) // root reached
+ break;
+ current = parent;
+ }
+
+ // Check the target path itself (if it's a file)
+ if (File.Exists(path))
+ {
+ var fileInfo = new FileInfo(path);
+ if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
+ return true;
+ }
+
+ return false;
+ }
+ catch
+ {
+ // Fail-closed: treat errors as suspicious
+ return true;
+ }
+ }
}
}
diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
index 4eb9d0d9..ecb9d0c1 100644
--- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
+++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt
@@ -1 +1 @@
-580
+581
diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Feedback/FeedbackFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Feedback/FeedbackFunction.cs
index 10cb1a1e..4903d4e5 100644
--- a/src/Backend/AutopilotMonitor.Functions/Functions/Feedback/FeedbackFunction.cs
+++ b/src/Backend/AutopilotMonitor.Functions/Functions/Feedback/FeedbackFunction.cs
@@ -135,6 +135,16 @@ namespace AutopilotMonitor.Functions.Functions.Feedback
var principal = req.FunctionContext.GetUser();
string displayName = principal?.GetDisplayName() ?? upn;
+ // Request body size limit (1 MB)
+ if (req.Headers.TryGetValues("Content-Length", out var clValues)
+ && long.TryParse(clValues.FirstOrDefault(), out var contentLength)
+ && contentLength > 1_048_576)
+ {
+ var tooLarge = req.CreateResponse(HttpStatusCode.BadRequest);
+ await tooLarge.WriteAsJsonAsync(new { success = false, message = "Request body too large" });
+ return tooLarge;
+ }
+
var body = await req.ReadFromJsonAsync();
if (body == null)
{
diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Ingest/ReportAgentErrorFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Ingest/ReportAgentErrorFunction.cs
index 74c1092c..04d93dd2 100644
--- a/src/Backend/AutopilotMonitor.Functions/Functions/Ingest/ReportAgentErrorFunction.cs
+++ b/src/Backend/AutopilotMonitor.Functions/Functions/Ingest/ReportAgentErrorFunction.cs
@@ -101,6 +101,15 @@ namespace AutopilotMonitor.Functions.Functions.Ingest
///
internal async Task ProcessReportErrorAsync(HttpRequestData req, string tenantId)
{
+ // Request body size limit (1 MB)
+ if (req.Headers.TryGetValues("Content-Length", out var clValues)
+ && long.TryParse(clValues.FirstOrDefault(), out var contentLength)
+ && contentLength > 1_048_576)
+ {
+ _logger.LogWarning("ReportAgentError: Request body too large ({ContentLength} bytes) from tenant {TenantId}", contentLength, tenantId);
+ return req.CreateResponse(HttpStatusCode.OK); // Still 200 — agent must not retry
+ }
+
// Parse the report body
AgentErrorReport? report = null;
try
diff --git a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/SubmitSessionReportFunction.cs b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/SubmitSessionReportFunction.cs
index d5d984ed..fa0cb6ae 100644
--- a/src/Backend/AutopilotMonitor.Functions/Functions/Reports/SubmitSessionReportFunction.cs
+++ b/src/Backend/AutopilotMonitor.Functions/Functions/Reports/SubmitSessionReportFunction.cs
@@ -49,6 +49,16 @@ namespace AutopilotMonitor.Functions.Functions.Reports
// Still needed: GA check for audit log skip logic
var isGlobalAdmin = await _globalAdminService.IsGlobalAdminAsync(userIdentifier);
+ // Request body size limit (1 MB)
+ if (req.Headers.TryGetValues("Content-Length", out var clValues)
+ && long.TryParse(clValues.FirstOrDefault(), out var contentLength)
+ && contentLength > 1_048_576)
+ {
+ var tooLarge = req.CreateResponse(HttpStatusCode.BadRequest);
+ await tooLarge.WriteAsJsonAsync(new { success = false, message = "Request body too large" });
+ return tooLarge;
+ }
+
// Parse request body
var request = await req.ReadFromJsonAsync();
if (request == null)