Severity
Info / Minor (Performance)
Location
src/Servy.Core/Helpers/ProcessHelper.cs lines 319
Code
```csharp
public string? ResolvePath(string? inputPath)
{
...
var expandedPath = Environment.ExpandEnvironmentVariables(inputPath);
// 2. Strict Check: If the path still contains %, expansion likely failed
var match = Regex.Match(expandedPath, @\"%[^%]+%\");
if (match.Success)
...
}
```
Explanation
Regex.Match(string, string) with an inline pattern recompiles the regex on every call (or hits the small cached compiled-pattern dictionary inside Regex, depending on framework). ResolvePath is hot — it is called once per imported config field, once per ValidatePath, and once per service install/import action.
Across a large import (hundreds of services × ~12 path fields each), that's thousands of regex compilations or cache lookups for what is a single static pattern.
Compare with Service.cs:38-41, where the project already uses the canonical pattern: a static readonly Regex field with RegexOptions.Compiled and a MatchTimeout to harden against ReDoS:
```csharp
private static readonly Regex EnvVarPlaceholderRegex = new Regex(
@"(%[a-zA-Z_][a-zA-Z0-9_]*%)",
RegexOptions.Compiled,
AppConfig.InputRegexTimeout);
```
Suggested fix
Extract the pattern to a static readonly field with RegexOptions.Compiled and AppConfig.InputRegexTimeout:
```csharp
private static readonly Regex UnexpandedEnvVarRegex = new Regex(
@"%[^%]+%",
RegexOptions.Compiled,
AppConfig.InputRegexTimeout);
// then in ResolvePath:
var match = UnexpandedEnvVarRegex.Match(expandedPath);
```
This also adds the existing project-wide ReDoS timeout protection that the inline call lacks.
Severity
Info / Minor (Performance)
Location
src/Servy.Core/Helpers/ProcessHelper.cslines 319Code
```csharp
public string? ResolvePath(string? inputPath)
{
...
var expandedPath = Environment.ExpandEnvironmentVariables(inputPath);
}
```
Explanation
Regex.Match(string, string)with an inline pattern recompiles the regex on every call (or hits the small cached compiled-pattern dictionary insideRegex, depending on framework).ResolvePathis hot — it is called once per imported config field, once perValidatePath, and once per service install/import action.Across a large
import(hundreds of services × ~12 path fields each), that's thousands of regex compilations or cache lookups for what is a single static pattern.Compare with Service.cs:38-41, where the project already uses the canonical pattern: a
static readonly Regexfield withRegexOptions.Compiledand aMatchTimeoutto harden against ReDoS:```csharp
private static readonly Regex EnvVarPlaceholderRegex = new Regex(
@"(%[a-zA-Z_][a-zA-Z0-9_]*%)",
RegexOptions.Compiled,
AppConfig.InputRegexTimeout);
```
Suggested fix
Extract the pattern to a static readonly field with
RegexOptions.CompiledandAppConfig.InputRegexTimeout:```csharp
private static readonly Regex UnexpandedEnvVarRegex = new Regex(
@"%[^%]+%",
RegexOptions.Compiled,
AppConfig.InputRegexTimeout);
// then in ResolvePath:
var match = UnexpandedEnvVarRegex.Match(expandedPath);
```
This also adds the existing project-wide ReDoS timeout protection that the inline call lacks.