diff --git a/src/RemoteAgent/Files/FileService.cs b/src/RemoteAgent/Files/FileService.cs
index 0e2e998..8635489 100644
--- a/src/RemoteAgent/Files/FileService.cs
+++ b/src/RemoteAgent/Files/FileService.cs
@@ -75,7 +75,10 @@ private async Task HandleAsync(HttpListenerContext ctx)
}
catch (Exception ex)
{
- try { ctx.Response.StatusCode = 500; var b = Encoding.UTF8.GetBytes(ex.Message); ctx.Response.OutputStream.Write(b); ctx.Response.Close(); } catch { /* client gone */ }
+ // Log the detail server-side; return a generic, non-reflective body so no request-controlled
+ // data is echoed back into the response.
+ logger.LogWarning(ex, "File service request failed.");
+ try { ctx.Response.StatusCode = 500; ctx.Response.ContentType = "text/plain; charset=utf-8"; var b = Encoding.UTF8.GetBytes("Operation failed."); ctx.Response.OutputStream.Write(b); ctx.Response.Close(); } catch { /* client gone */ }
}
}
@@ -139,7 +142,8 @@ private static async Task WriteJsonAsync(HttpListenerContext ctx, FsList list)
private static void Ok(HttpListenerContext ctx) { ctx.Response.StatusCode = 200; ctx.Response.Close(); }
- private void Audit(string op, string detail) => logger.LogInformation("file {Op}: {Detail}", op, detail);
+ // Strip CR/LF from the request-controlled detail so a crafted path cannot forge extra audit-log lines.
+ private void Audit(string op, string detail) => logger.LogInformation("file {Op}: {Detail}", op, detail.Replace('\r', ' ').Replace('\n', ' '));
/// Logged-in user's home (best-effort): the system drive's Users\<active console user>, else the drive root.
private static string HomeDir()
diff --git a/src/RemoteAgent/RemoteAgent.csproj b/src/RemoteAgent/RemoteAgent.csproj
index 786ac1c..e57f9b6 100644
--- a/src/RemoteAgent/RemoteAgent.csproj
+++ b/src/RemoteAgent/RemoteAgent.csproj
@@ -7,7 +7,7 @@
enable
RemoteAgent
RemoteAgent
- 1.7.0.0
+ 1.7.1.0
..\..\icon\app.ico
diff --git a/src/RemoteAgent/Services/VncProvisioningService.cs b/src/RemoteAgent/Services/VncProvisioningService.cs
index 714c298..0d1b81b 100644
--- a/src/RemoteAgent/Services/VncProvisioningService.cs
+++ b/src/RemoteAgent/Services/VncProvisioningService.cs
@@ -1,4 +1,6 @@
using System.Net.Http.Json;
+using System.Security.Cryptography;
+using System.Text;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -37,7 +39,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
try
{
var secretFile = Path.Combine(_opt.EnrollmentDir, "vnc.secret");
- string? password = File.Exists(secretFile) ? File.ReadAllText(secretFile).Trim() : null;
+ string? password = ReadSecret(secretFile);
var msi = Path.Combine(AppContext.BaseDirectory, "vnc", "tightvnc.msi");
@@ -48,7 +50,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await VncProvisioner.EnsureInstalledAsync(msi);
VncProvisioner.ApplyHardening(password);
- File.WriteAllText(secretFile, password);
+ WriteSecret(secretFile, password);
logger.LogInformation(L.VncProvisioningService_VNCProvisionedPerDevicePassword);
}
catch (Exception ex)
@@ -87,7 +89,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
try
{
var secretFile = Path.Combine(_opt.EnrollmentDir, "vnc.secret");
- var pw = File.Exists(secretFile) ? File.ReadAllText(secretFile).Trim() : null;
+ var pw = ReadSecret(secretFile);
if (!string.IsNullOrEmpty(pw))
await VncProvisioner.EnsureHealthyAsync(pw, msiPath);
}
@@ -95,6 +97,25 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
}
+ // vnc.secret stores the local VNC password. Encrypt it at rest with DPAPI (machine scope; the agent
+ // runs as SYSTEM), transparently migrating any legacy plaintext file the first time it is read.
+ private static string? ReadSecret(string file)
+ {
+ if (!File.Exists(file)) return null;
+ var raw = File.ReadAllBytes(file);
+ if (raw.Length == 0) return null;
+ try { return Encoding.UTF8.GetString(ProtectedData.Unprotect(raw, null, DataProtectionScope.LocalMachine)).Trim(); }
+ catch
+ {
+ var plain = Encoding.UTF8.GetString(raw).Trim(); // legacy plaintext
+ try { WriteSecret(file, plain); } catch { /* best-effort migration */ }
+ return plain;
+ }
+ }
+
+ private static void WriteSecret(string file, string password) =>
+ File.WriteAllBytes(file, ProtectedData.Protect(Encoding.UTF8.GetBytes(password), null, DataProtectionScope.LocalMachine));
+
private async Task ReportSecretAsync(string password, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(_opt.Telemetry.IngestUrl))