You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A comprehensive review of the RoslynRules codebase identified multiple issues across security, memory management, thread safety, documentation accuracy, and API design. This issue consolidates all findings for batch fixing.
🔴 Critical
1. Security — Code Injection Risk
User-supplied expression strings are embedded raw into generated C# code without sanitization. A malicious expression like "); File.Delete(@"C:\data"); //" can execute arbitrary code.
Root cause:CodeGenerator.Generate() interpolates the raw expression string directly into the generated method body. AssemblyCompiler.BuildReferences() loads ALL currently loaded assemblies including System.IO, System.Diagnostics.Process, etc., giving expressions broad runtime access.
Fix needed:
Document the risk prominently (SECURITY.md + README warning)
Consider restricting loaded assemblies to a whitelist
Add Roslyn syntax tree validation to reject dangerous expression patterns (invocations on File, Process, etc.)
Every unique expression compilation loads a new assembly into the current AppDomain via Assembly.Load(assemblyBytes). These assemblies are never unloaded. In a long-running app with dynamic rule generation, this causes unbounded memory growth.
Root cause: Standard Assembly.Load loads into the default AppDomain. .NET Framework cannot unload individual assemblies. .NET Core+ supports AssemblyLoadContext for collectible assemblies but it is not used.
Fix needed:
Use AssemblyLoadContext for .NET Core+ to enable assembly unloading
Or document this as a known limitation with mitigation guidance (restart thresholds, expression reuse)
Workflow.ExecuteParallel() does NOT use GetExecutionOrder(). It runs all active rules in parallel sorted only by priority, skipping the dependency chain entirely. Rules with DependsOnRuleId may execute before their dependencies, causing RuleContext misses and incorrect results.
Note:ExecuteParallelAsync() handles dependencies correctly. The sync version should match that behavior.
Fix needed:
Make ExecuteParallel use GetExecutionOrder() and execute independent rules in parallel while respecting dependency ordering
In ExpressionCompiler.Compile<TDelegate>(), the cache check and write are in separate locks. Two threads can compile the same expression simultaneously. The second thread's result overwrites the first in cache. Not dangerous (same result), but wastes CPU.
Rule.ExecuteCore() uses Task.Run(() => ExecuteCoreInternal(...)) then blocks with .Wait() for timeout handling. This wastes thread-pool threads. Same pattern in CompiledAsyncFunc.Invoke() which blocks on .GetAwaiter().GetResult() — potential for thread-pool starvation and deadlocks in ASP.NET / UI contexts.
Fix needed:
Replace Task.Run + .Wait() with CancellationTokenSource.CancelAfter() for timeout
Document that sync execution of async delegates blocks; recommend ExecuteAsync() for async expressions
Add [Obsolete("Use ExecuteAsync for async expressions to avoid blocking.")] or analyzer warning
Child rules are part of the cached RuleResult but docs say "Child rules are still evaluated independently" — clarify that child rules ARE evaluated independently but their results ARE cached as part of the parent result
RuleContext.StoreResult is public but intended for internal engine use — should be documented as "for internal use; do not call directly"
9. Missing Project Files
CONTRIBUTING.md — contributor guidelines
SECURITY.md — security policy and expression injection disclosure
CHANGELOG.md — version history and breaking changes
10. JSON Loader Uses Reflection to Bypass Private Setters
JsonRuleLoader uses typeof(Rule).GetProperty("Id")?.SetValue(...) to restore IDs from JSON. This is fragile — breaks with trimming/AOT and is sensitive to refactoring.
Fix needed:
Consider making Id init-only (init accessor) or adding a deserialization constructor
Or use [JsonInclude] attribute with private setters
A comprehensive review of the RoslynRules codebase identified multiple issues across security, memory management, thread safety, documentation accuracy, and API design. This issue consolidates all findings for batch fixing.
🔴 Critical
1. Security — Code Injection Risk
User-supplied expression strings are embedded raw into generated C# code without sanitization. A malicious expression like
"); File.Delete(@"C:\data"); //"can execute arbitrary code.Root cause:
CodeGenerator.Generate()interpolates the raw expression string directly into the generated method body.AssemblyCompiler.BuildReferences()loads ALL currently loaded assemblies includingSystem.IO,System.Diagnostics.Process, etc., giving expressions broad runtime access.Fix needed:
File,Process, etc.)Location:
RoslynRules/Compiler/CodeGenerator.cs,RoslynRules/Compiler/AssemblyCompiler.cs2. Memory Leak — Assembly Unloading
Every unique expression compilation loads a new assembly into the current AppDomain via
Assembly.Load(assemblyBytes). These assemblies are never unloaded. In a long-running app with dynamic rule generation, this causes unbounded memory growth.Root cause: Standard
Assembly.Loadloads into the default AppDomain. .NET Framework cannot unload individual assemblies. .NET Core+ supportsAssemblyLoadContextfor collectible assemblies but it is not used.Fix needed:
AssemblyLoadContextfor .NET Core+ to enable assembly unloadingLocation:
RoslynRules/Compiler/DelegateFactory.cs:15,RoslynRules/Compiler/AssemblyCompiler.cs:73🟡 High
3. Workflow.ExecuteParallel Ignores Dependencies
Workflow.ExecuteParallel()does NOT useGetExecutionOrder(). It runs all active rules in parallel sorted only by priority, skipping the dependency chain entirely. Rules withDependsOnRuleIdmay execute before their dependencies, causingRuleContextmisses and incorrect results.Note:
ExecuteParallelAsync()handles dependencies correctly. The sync version should match that behavior.Fix needed:
ExecuteParalleluseGetExecutionOrder()and execute independent rules in parallel while respecting dependency orderingLocation:
RoslynRules/Models/Workflow.cs(~line 312)4. ExpressionCompiler Caching Race Condition
In
ExpressionCompiler.Compile<TDelegate>(), the cache check and write are in separate locks. Two threads can compile the same expression simultaneously. The second thread's result overwrites the first in cache. Not dangerous (same result), but wastes CPU.Current code:
Fix needed:
ConcurrentDictionary.GetOrAdd()with a factory delegate, ORLocation:
RoslynRules/Compiler/ExpressionCompiler.cs:47-525. Sync-over-Async Blocking
Rule.ExecuteCore()usesTask.Run(() => ExecuteCoreInternal(...))then blocks with.Wait()for timeout handling. This wastes thread-pool threads. Same pattern inCompiledAsyncFunc.Invoke()which blocks on.GetAwaiter().GetResult()— potential for thread-pool starvation and deadlocks in ASP.NET / UI contexts.Fix needed:
Task.Run+.Wait()withCancellationTokenSource.CancelAfter()for timeoutExecuteAsync()for async expressions[Obsolete("Use ExecuteAsync for async expressions to avoid blocking.")]or analyzer warningLocations:
RoslynRules/Models/Rule.cs:456-469(sync timeout)RoslynRules/Models/CompiledDelegate.cs:77-79(CompiledAsyncFunc.Invoke)RoslynRules/Models/CompiledDelegate.cs:108-110(CompiledAsyncAction.Invoke)🟢 Medium — Documentation
6. Duplicate Result Caching Section in README
The "Result Caching" section appears twice in README.md.
Fix: Remove the duplicate.
7. Missing API Reference Entries
The following public APIs are undocumented in
docs/api-reference.md:Rule.ClearCache()— forces cache invalidationRule.ExecuteAsync()— async rule executionWorkflow.ExecuteBufferedAsync()— buffered async executionRuleBatchinterface methods — missing mostIRuleEnginealiases8. Incorrect Documentation
RulesExceptioninheritance: docs say inheritsInvalidOperationException, actually inheritsExceptionWorkflow.ExecuteParalleldocs claim "respects dependency chains" — it does not (see item Bump DynamicExpresso.Core from 2.19.2 to 2.19.3 #3)RuleResultbut docs say "Child rules are still evaluated independently" — clarify that child rules ARE evaluated independently but their results ARE cached as part of the parent resultRuleContext.StoreResultis public but intended for internal engine use — should be documented as "for internal use; do not call directly"9. Missing Project Files
CONTRIBUTING.md— contributor guidelinesSECURITY.md— security policy and expression injection disclosureCHANGELOG.md— version history and breaking changes10. JSON Loader Uses Reflection to Bypass Private Setters
JsonRuleLoaderusestypeof(Rule).GetProperty("Id")?.SetValue(...)to restore IDs from JSON. This is fragile — breaks with trimming/AOT and is sensitive to refactoring.Fix needed:
Idinit-only (initaccessor) or adding a deserialization constructor[JsonInclude]attribute with private settersLocation:
RoslynRules/Extensions/JsonRuleLoader.cs:86-89,153-15611. Null-Forgiving Operator on Reflection Results
DelegateFactoryuses!on reflection results that could legitimately be null if generated code doesn't match expectations:Fix needed: Replace with null checks and meaningful exceptions.
Location:
RoslynRules/Compiler/DelegateFactory.cs:19-20🟢 Medium — Tests
12. Missing Test Coverage
RuleParameter.Value, deeply nested JSON round-trip, empty string expressions with only whitespaceAcceptance Criteria
Workflow.ExecuteParallelfixed to respect dependency chains OR documented as limitationCancellationTokenSource.CancelAfter()instead ofTask.Run+.Wait()Rule.ClearCache(),Rule.ExecuteAsync(),Workflow.ExecuteBufferedAsync()documented in api-reference.mdRulesExceptioninheritance docs correctedJsonRuleLoaderreflection replaced with init-only setters or[JsonInclude]DelegateFactorynull-forgiving operators replaced with null checksEstimated Effort
ExecuteParalleldependency fix: 2-3 hoursJsonRuleLoaderrefactor: 1-2 hours