Secure Dispatch: Replace String Reflection with a Typed Registry

Summary

An engineer attempted to implement a dynamic dispatch mechanism by converting a URL string directly into a class type for instantiation. While the intent was to avoid a massive switch statement or a repetitive list of manual instantiations, the proposed approach—using string names to drive object creation—introduced a critical architectural vulnerability and a high risk of runtime instability.

Root Cause

The core issue is the attempt to bypass type safety in favor of string-based reflection. The proposed logic follows this pattern:

  • Input-Driven Instantiation: Taking an unvalidated or loosely validated string from a request and mapping it directly to a class name.
  • Lack of Interface Enforcement: The proposed new <requestedClass>() pattern does not guarantee that the resulting object possesses the required EntryPoint method, leading to potential MissingMethodException errors at runtime.
  • Tight Coupling to Naming: The system’s logic becomes coupled to the literal naming conventions of the codebase, making refactoring nearly impossible without breaking the routing logic.

Why This Happens in Real Systems

In complex, distributed systems, developers often encounter “The Expression Problem”—the difficulty of adding new functionality without modifying existing code.

  • Avoidance of Boilerplate: Developers see manual if/else or switch blocks as “code smell” and seek automated ways to map inputs to logic.
  • Misunderstanding Reflection: There is a common misconception that Reflection is a tool for “magic” automation, rather than a specialized tool that should be used sparingly and under strict guardrails.
  • Premature Abstraction: Attempting to build a “universal” router before defining a stable contract (interface) leads to patterns that are hard to debug and easy to exploit.

Real-World Impact

  • Remote Code Execution (RCE): If the string parsing is not strictly sandboxed, an attacker could potentially pass the name of a sensitive system class (e.g., System.Diagnostics.Process) to execute arbitrary commands on the server.
  • Denial of Service (DoS): An attacker could trigger the instantiation of extremely “heavy” objects, exhausting server memory and CPU cycles.
  • Brittle Deployments: Renaming a class during a routine refactor would silently break the routing logic, leading to production outages that are difficult to catch during unit testing.

Example or Code (if necessary and relevant)

public interface IRequestHandler
{
    string Execute(Dictionary context);
}

public class RouteRegistry
{
    private readonly Dictionary _routes = new();

    public void Register(string routeName) where T : IRequestHandler
    {
        _routes[routeName] = typeof(T);
    }

    public string HandleRequest(string routeName, Dictionary context)
    {
        if (!_routes.TryGetValue(routeName, out var type))
        {
            throw new ArgumentException("Invalid route");
        }

        var instance = (IRequestHandler)Activator.CreateInstance(type);
        return instance.Execute(context);
    }
}

How Senior Engineers Fix It

Senior engineers solve this by moving from dynamic string-to-type mapping to explicit registry-based mapping.

  • Define a Contract: Instead of assuming a class has an EntryPoint, create an IRequestHandler interface. This ensures compile-time safety.
  • Use a Lookup Table (Registry): Instead of allowing any string to map to any class, maintain a Dictionary<string, Type> or Dictionary<string, IRequestHandler>. This acts as an Allow-List, preventing attackers from instantiating unauthorized classes.
  • Dependency Injection (DI): Rather than using new inside the handler, use a DI container to resolve the requested service. This allows for better testing and manages object lifetimes correctly.
  • Separation of Concerns: The RouteMaster should only be responsible for matching a path to a key, not for the direct instantiation of logic.

Why Juniors Miss It

  • Focus on Functionality over Security: Juniors often prioritize “making it work” (the happy path) and assume that if the frontend is “protected,” the backend is inherently safe.
  • The “Magic” Trap: There is a psychological appeal to “magic” code that handles everything automatically. It feels more sophisticated than a simple Dictionary or switch.
  • Ignoring the Contract: Juniors often overlook the importance of Interfaces. They view them as extra boilerplate rather than the essential safety mechanism that prevents runtime crashes in production.

Leave a Comment