AI-generated audit finding — this issue was opened from an automated security/correctness audit. It has not been triaged by a human yet; verify the reasoning, reproducibility, and severity before acting on it.
Medium: named server registration can cross-wire dispatchers when the same optionsName is reused — CONFIRMED
Affected code:
Verification:
Confirmed. Two failure modes arise from the same root cause (shared mutable ServerOptions instance per optionsName):
AddIceRpcServer(optionsName, IDispatcher) (line 93-101) wires the dispatcher through IConfigureOptions<ServerOptions> at line 99. Two calls with the same name install two Configure callbacks on the same named options; the last one wins when the options are built.
AddIceRpcServer(optionsName, Action<IDispatcherBuilder>) (line 113-132) resolves the ServerOptions from IOptionsMonitor<ServerOptions>.Get(optionsName) — which returns the same cached instance on every call — and mutates options.ConnectionOptions.Dispatcher at line 125 directly. Two such registrations produce two Server singletons that share one ServerOptions / ConnectionOptions; both listeners end up routing into whichever dispatcher wrote last.
Because DI containers commonly return the last registration for a non-enumerable GetRequiredService<Server>(), the first AddIceRpcServer call's factory might never run at resolve time — but any code that enumerates (GetServices<Server>(), tests, IHostedService registration of both) runs both factories and leaves the options in the last-written state.
Impact:
- Requests dispatched to the wrong service graph on the wrong listener.
- Endpoint exposure can drift silently from the configured server address, since dispatchers may encode routing/authorization assumptions.
- The bug is invisible at registration time and only surfaces at runtime.
Recommendation:
- Clone
ServerOptions (and its nested ConnectionOptions) before mutating Dispatcher inside the factory. Server could also defensively copy options in its constructor, but the DI package owns the named-options contract so the fix belongs here.
- Reject duplicate server registrations for the same
optionsName, or document/enforce that names must be unique. A simple TryAdd-style guard would catch the common case.
- Add tests covering two server registrations with the same name and asserting that each receives its own dispatcher.
Status: Valid, Medium severity.
Source report: src-IceRpc.Extensions.DependencyInjection-audit-2026-04-14.md (finding named server registration can cross-wire dispatchers when the same optionsName is reused — **CONFIRMED**)
Severity (auditor-assigned): Medium
Medium: named server registration can cross-wire dispatchers when the same
optionsNameis reused — CONFIRMEDAffected code:
AddOptions<ServerOptions>(optionsName).Configure(o => o.ConnectionOptions.Dispatcher = dispatcher)options.ConnectionOptions.Dispatcher = dispatcherBuilder.Build();Serverstores the suppliedServerOptionsreference without defensive copy; listeners and accepted protocol connections snapshotConnectionOptions.Dispatcherfrom that shared objectVerification:
Confirmed. Two failure modes arise from the same root cause (shared mutable
ServerOptionsinstance peroptionsName):AddIceRpcServer(optionsName, IDispatcher)(line 93-101) wires the dispatcher throughIConfigureOptions<ServerOptions>at line 99. Two calls with the same name install twoConfigurecallbacks on the same named options; the last one wins when the options are built.AddIceRpcServer(optionsName, Action<IDispatcherBuilder>)(line 113-132) resolves theServerOptionsfromIOptionsMonitor<ServerOptions>.Get(optionsName)— which returns the same cached instance on every call — and mutatesoptions.ConnectionOptions.Dispatcherat line 125 directly. Two such registrations produce twoServersingletons that share oneServerOptions/ConnectionOptions; both listeners end up routing into whichever dispatcher wrote last.Because DI containers commonly return the last registration for a non-enumerable
GetRequiredService<Server>(), the firstAddIceRpcServercall's factory might never run at resolve time — but any code that enumerates (GetServices<Server>(), tests, IHostedService registration of both) runs both factories and leaves the options in the last-written state.Impact:
Recommendation:
ServerOptions(and its nestedConnectionOptions) before mutatingDispatcherinside the factory.Servercould also defensively copy options in its constructor, but the DI package owns the named-options contract so the fix belongs here.optionsName, or document/enforce that names must be unique. A simpleTryAdd-style guard would catch the common case.Status: Valid, Medium severity.
Source report: src-IceRpc.Extensions.DependencyInjection-audit-2026-04-14.md (finding
named server registration can cross-wire dispatchers when the sameoptionsNameis reused — **CONFIRMED**)Severity (auditor-assigned): Medium