Severity: Info (export-time validation, edge case — but the reserved-names list is supposed to be exhaustive and currently isn't)
File / line: src/Servy.CLI/Commands/ExportServiceCommand.cs lines 24–36
Code:
private static readonly HashSet<string> ReservedDeviceNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"CON", "PRN", "AUX", "NUL" // <-- complete (just 4 entries)
};
private static readonly Regex ReservedPortRegex = new Regex(
@"^(COM|LPT)[1-9]$", // <-- only [1-9], excludes 0 and superscripts
RegexOptions.Compiled | RegexOptions.IgnoreCase,
AppConfig.InputRegexTimeout);
What's wrong: The list of Windows reserved device names used by the export safety check is incomplete relative to the current Microsoft naming-a-file documentation. Microsoft's authoritative list is:
CON, PRN, AUX, NUL, COM0, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, COM¹, COM², COM³, LPT0, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9, LPT¹, LPT², LPT³
The regex ^(COM|LPT)[1-9]$ rejects COM1–COM9 and LPT1–LPT9 but accepts COM0/LPT0 (CharRange [1-9] excludes 0). It also accepts the Unicode superscript variants COM¹, COM², COM³, LPT¹, LPT², LPT³ — these were added to the official reserved list in newer Microsoft docs.
A user running servy-cli export --name MyService --config xml --path "C:\backup\COM0.xml" would pass Path.GetFileNameWithoutExtension(...) == "COM0", fall through both ReservedDeviceNames.Contains(...) and the regex match, and the export would proceed. On most Windows builds the actual File.WriteAllText would either succeed (writing a regular file with a confusing name) or — on certain builds and certain APIs — silently redirect the write to a device handle. Either way the validation isn't doing what its comment promises.
Suggested fix: Either widen the regex to include 0 and the superscripts, or fold the whole thing into a single canonical helper. A minimal regex fix:
private static readonly Regex ReservedPortRegex = new Regex(
@"^(COM|LPT)([0-9]|¹|²|³)$",
RegexOptions.Compiled | RegexOptions.IgnoreCase,
AppConfig.InputRegexTimeout);
Or, since the full list is small and finite, drop the regex entirely and put everything in the HashSet — easier to audit against the docs:
private static readonly HashSet<string> ReservedDeviceNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"CON", "PRN", "AUX", "NUL",
"COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"COM¹", "COM²", "COM³", // ¹ ² ³
"LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
"LPT¹", "LPT²", "LPT³",
};
// ...later:
if (ReservedDeviceNames.Contains(fileName))
throw new ArgumentException($"Security Alert: '{fileName}' is a reserved Windows device name and cannot be used.");
The HashSet form also drops the RegexMatchTimeoutException fallback at line 180–184, which is overkill for a name-equality check.
Severity: Info (export-time validation, edge case — but the reserved-names list is supposed to be exhaustive and currently isn't)
File / line:
src/Servy.CLI/Commands/ExportServiceCommand.cslines 24–36Code:
What's wrong: The list of Windows reserved device names used by the export safety check is incomplete relative to the current Microsoft naming-a-file documentation. Microsoft's authoritative list is:
The regex
^(COM|LPT)[1-9]$rejectsCOM1–COM9andLPT1–LPT9but acceptsCOM0/LPT0(CharRange[1-9]excludes0). It also accepts the Unicode superscript variantsCOM¹,COM²,COM³,LPT¹,LPT²,LPT³— these were added to the official reserved list in newer Microsoft docs.A user running
servy-cli export --name MyService --config xml --path "C:\backup\COM0.xml"would passPath.GetFileNameWithoutExtension(...) == "COM0", fall through bothReservedDeviceNames.Contains(...)and the regex match, and the export would proceed. On most Windows builds the actualFile.WriteAllTextwould either succeed (writing a regular file with a confusing name) or — on certain builds and certain APIs — silently redirect the write to a device handle. Either way the validation isn't doing what its comment promises.Suggested fix: Either widen the regex to include
0and the superscripts, or fold the whole thing into a single canonical helper. A minimal regex fix:Or, since the full list is small and finite, drop the regex entirely and put everything in the HashSet — easier to audit against the docs:
The HashSet form also drops the
RegexMatchTimeoutExceptionfallback at line 180–184, which is overkill for a name-equality check.