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
{{ message }}
Repository navigation
Plugin fails to load on gateway 25.4.0: "Could not load type IDomainValidatorFactory" from Keyfactor.AnyGateway.IAnyCAPlugin v3.2.0.0 #7
The CERTInext caplugin DLL fails to load inside the AnyCA REST Gateway image 25.4.0 (chart anygateway-rest 1.0.1) with a missing-type error against Keyfactor.AnyGateway.IAnyCAPlugin.
Stage and load `CERTInext/bin/Release/net10.0/CERTInextCAPlugin.dll` (built locally from the current `main` of this repo) into the gateway pod's `/app/Extensions//` directory.
POST a CA registration to `/AnyGatewayREST/config/configuration` referencing the plugin.
Actual behavior
Gateway returns HTTP 500 with body `{"ErrorCode":"0x80131509"}`. Gateway pod logs show:
```
Could not load type 'Keyfactor.AnyGateway.Extensions.IDomainValidatorFactory'
from assembly 'Keyfactor.AnyGateway.IAnyCAPlugin, Version=3.2.0.0, Culture=neutral, PublicKeyToken=0ed89d330114ab09'.
at System.Signature..ctor(...)
at System.Reflection.RuntimeConstructorInfo.<get_Signature>g__LazyCreateSignature|21_0()
at System.Reflection.RuntimeConstructorInfo.GetParameters()
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c.b__27_0(...)
```
The plugin's manifest references the type `Keyfactor.Extensions.CAPlugin.CERTInext.CERTInextCAPlugin` whose constructor (directly or transitively) depends on `IDomainValidatorFactory`. That interface does not exist in the version of `Keyfactor.AnyGateway.IAnyCAPlugin` shipped in gateway image 25.4.0.
Expected behavior
The DLL loads cleanly on a supported gateway version.
The declared framework target is `24.2.0`, but the plugin code currently consumes a type (`IDomainValidatorFactory`) that exists in a NEWER framework. Either the `gateway_framework` field is out of date OR the plugin needs to either back-port off `IDomainValidatorFactory` or pin its `Keyfactor.AnyGateway.IAnyCAPlugin` NuGet to a version that contains the type.
Plugin DLL size: 171520 bytes (built locally from `main`)
Discovered via the kfclab lab orchestrator's generic plugin pipeline; orchestrator itself is plugin-agnostic, so the failure surfaces in the gateway pod cleanly.
Suggested fix
Pin or bump the `Keyfactor.AnyGateway.IAnyCAPlugin` NuGet reference in `CERTInext/CERTInext.csproj` to a version that includes `IDomainValidatorFactory`, and update `integration-manifest.json#gateway_framework` to match the matching gateway release. Alternately, refactor the plugin to make `IDomainValidatorFactory` an optional dependency.
The only public constructor on CERTInextCAPlugin is now parameterless. All test-injection constructors that referenced IDomainValidatorFactory were demoted to internal (the test projects reach them via [InternalsVisibleTo]). With this change, a v3.2 gateway host's DI container no longer trips TypeLoadException at GetParameters() — there's no missing-type reference on any constructor signature it walks.
Added a public method SetDomainValidatorFactory(object factory) so v3.3+ gateway hosts (or any host that can resolve the factory from their own service container) can wire up DCV after construction. The parameter type is object so the method's own signature stays loadable on older gateways; the cast to IDomainValidatorFactory happens inside the JIT-lazy method body.
All DCV code paths null-guard the factory, so plugin behaviour on v3.2 hosts is: load cleanly, enroll / sync / revoke normally, and silently skip DCV (each pending order is returned in EXTERNALVALIDATION state for the gateway to pick up on the next sync — exactly the same shape as a 'pending approval' result today).
New regression test CERTInextCAPluginPublicSurfaceTests walks public constructors via reflection and asserts none of their parameters reference IDomainValidatorFactory, IDomainValidator, or IDomainValidatorConfigProvider. Any future PR that puts a v3.3-only type back on a public constructor will fail this test instead of crashing live plugin load.
What was NOT changed
integration-manifest.json#gateway_framework remains 24.2.0 and the README's compatibility line remains '24.2.0 and later'. Both are now accurate post-fix (previously they were aspirational — the plugin would not have loaded on a v3.2 host).
The Keyfactor.AnyGateway.IAnyCAPlugin NuGet pin remains at the v3.3 prerelease so the plugin can still use IDomainValidatorFactory internally on hosts that supply it.
Verification
Local build clean against both net8.0 and net10.0 TFMs.
142/142 unit tests pass (5 new public-surface tests + 137 existing).
Live verification against a 25.4.0 gateway is the next step; can be picked up in the same lab orchestrator pipeline that surfaced this issue.
Reporting back from a live re-test on gateway 25.5.0 (ships Keyfactor.AnyGateway.IAnyCAPlugin v3.2.0.0) against the post-fix build that includes ce8e02d. Initialize loads cleanly and CA registration works, but the same TypeLoadException on IDomainValidatorFactory still fires when the gateway calls Initialize during enrollment — i.e. PFX/CSR enrolls return HTTP 500 with the same stack trace shape we saw before the fix.
.NET JIT resolves the types of every instance field when the containing class is first instantiated — independent of whether any constructor parameter or method signature mentions the type. So the runtime can fully construct the plugin via the public parameterless ctor (the PR #2 fix), but the very first time the host calls Initialize (or any other instance method), the JIT trips TypeLoadException resolving the field's declared type against the v3.2 assembly. PR #2 fixed constructor-signature loading but did not catch field-type loading.
Live evidence
Gateway 25.5.0 pod log (correlation id F3B7DC2F-E4E3-4554-818C-1C42DC3B1D2C):
2026-05-22 03:11:24.0728 AnyGatewayCore.Services.EndEntityService [Info] - Getting the necessary template information for enrollment from CA certinext-caplugin.
2026-05-22 03:11:24.0804 AnyGatewayREST.Controllers.CertificateV1Controller [Error] - Unsuccessful Request Could not load type 'Keyfactor.AnyGateway.Extensions.IDomainValidatorFactory' from assembly 'Keyfactor.AnyGateway.IAnyCAPlugin, Version=3.2.0.0, Culture=neutral, PublicKeyToken=0ed89d330114ab09'.
at Keyfactor.Extensions.CAPlugin.CERTInext.CERTInextCAPlugin.Initialize(IAnyCAPluginConfigProvider configProvider, ICertificateDataReader certificateDataReader) in CERTInextCAPlugin.cs:line 186
at AnyGatewayREST.Controllers.CertificateV1Controller.CertificateRequestEnroll(CertificateRequest certificateRequest) in /src/AnyGatewayREST/Controllers/CertificateV1Controller.cs:line 221
Note the stack trace lands at the start of Initialize — the body never executes; the JIT fails before the first instruction runs because resolving the _domainValidatorFactory field's declared type fails. This is the well-known "TypeLoadException happens the first time a method touches the class even if the missing type isn't on that line" behavior. The line number is the entry point of Initialize, not where the actual reference lives.
Initialization at CA-registration time works because the gateway's CA registration flow constructs the plugin and calls a different code path that doesn't require JIT-compiling Initialize (likely a metadata-only inspection path). It's only the enrollment path that requires Initialize to actually run, which is where the JIT trip happens.
Suggested fix
Type the field as object (matching the SetDomainValidatorFactory(object factory) parameter pattern PR #2 already established) and cast in method bodies on demand:
// CERTInextCAPlugin.cs line 48privatevolatileobject_domainValidatorFactory;// typed as object, not IDomainValidatorFactory
Then anywhere the field is consumed:
vartyped=_domainValidatorFactoryasIDomainValidatorFactory;if(typed!=null){/* use typed */}
The cast happens inside method bodies, which the JIT compiles lazily per-method, so the missing type is only resolved on v3.3+ hosts that supply it. Same pattern that already protects SetDomainValidatorFactory.
The existing regression test (CERTInextCAPluginPublicSurfaceTests) walks public constructors and would not catch this — it doesn't enumerate field declarations. Worth extending the reflection walk to assert no instance field's declared type references IDomainValidatorFactory / IDomainValidator / IDomainValidatorConfigProvider either.
Environment
Plugin DLL: locally built today from current main (sha256 b9f470b07e929f59186b6e7b44ad049d9a32f78987bf5ba0b3210b23e90a0429, 201728 bytes), verified in-pod via sha256sum /app/Extensions/certinext-caplugin/CERTInextCAPlugin.dll.
Discovered via the same kfclab lab orchestrator pipeline that surfaced the original issue; the gateway side of the lab now registers 8 CERTInext templates and the CA is registered in Command, so the test fixture is end-to-end and reproducible.
Field-type follow-up fix landed in commit 054b2c3 on PR #2. Your diagnosis was correct — constructor-signature reflection was the first JIT trip, instance-field declared-type resolution is the second.
What changed
CERTInextCAPlugin.cs:48 — _domainValidatorFactory field re-typed from volatile IDomainValidatorFactory to volatile object. The CLR no longer touches the v3.3-only type during class-load.
CERTInextCAPlugin.cs:63 — new private property DomainValidatorFactory returns _domainValidatorFactory as IDomainValidatorFactory. The cast is inside the property body so the JIT defers type resolution to the moment the property actually runs (on a v3.2 host where _domainValidatorFactory is null, the gating null-check short-circuits before the property is ever invoked, so the missing type is never touched).
Null guards (_domainValidatorFactory != null / == null) stay as direct field reads — comparing object to null doesn't touch the v3.3 type at all.
The dead-code nested helper DomainValidatorConfigProvider : IDomainValidatorConfigProvider was deleted. It was declared but never instantiated, and a nested type whose base list references a missing assembly type is itself a class-load hazard.
Regression coverage you asked for
CERTInextCAPluginPublicSurfaceTests (the reflection guard from the first pass) gained three new tests beyond the original constructor-signature walk:
NoInstanceField_DeclaredTypeReferencesV3Point3OnlyTypes — walks every instance field on CERTInextCAPlugin and asserts none is declared as IDomainValidatorFactory / IDomainValidator / IDomainValidatorConfigProvider. This is the exact reflection the JIT does at class-load.
NoPublicMethod_SignatureReferencesV3Point3OnlyTypes — walks return and parameter types of public methods.
Any future regression in any of those four reflection surfaces will fail at dotnet test instead of crashing live plugin load.
Verification
Release build clean (0 warnings, 0 errors) on both net8.0 and net10.0.
156/156 unit tests pass (8 in CERTInextCAPluginPublicSurfaceTests after the additions).
Live DCV-on enroll still completes end-to-end against the v3.3 sandbox we test against, so the DCV hot path still works where the factory IS available.
Local Release DLL: CERTInext/bin/Release/{net8.0,net10.0}/CERTInextCAPlugin.dll. The net8.0 is the one to drop into a 25.5.0 host.
Ready for another lab re-test when you have a window. If Initialize still trips, please grab the stack trace + the exact DLL sha256 you're loading and I'll dig further.
Confirmed working in the lab on gateway 25.5.0 (IAnyCAPlugin v3.2.0.0) — plugin loads, registers, enrolls, and runs end-to-end. Closing. Thanks for the precise repro + JIT trace; it pointed directly at the field-type leak that the first pass missed.
Activity
github-actions commentedon May 21, 2026
Thanks for opening this issue! Our team has been notified and will follow up here.
fix(plugin): make IDomainValidatorFactory injection optional (#7)
spbsoluble commentedon May 21, 2026
Fix landed on PR #2 in commit ce8e02d.
What changed
CERTInextCAPluginis now parameterless. All test-injection constructors that referencedIDomainValidatorFactorywere demoted tointernal(the test projects reach them via[InternalsVisibleTo]). With this change, a v3.2 gateway host's DI container no longer tripsTypeLoadExceptionatGetParameters()— there's no missing-type reference on any constructor signature it walks.SetDomainValidatorFactory(object factory)so v3.3+ gateway hosts (or any host that can resolve the factory from their own service container) can wire up DCV after construction. The parameter type isobjectso the method's own signature stays loadable on older gateways; the cast toIDomainValidatorFactoryhappens inside the JIT-lazy method body.EXTERNALVALIDATIONstate for the gateway to pick up on the next sync — exactly the same shape as a 'pending approval' result today).CERTInextCAPluginPublicSurfaceTestswalks public constructors via reflection and asserts none of their parameters referenceIDomainValidatorFactory,IDomainValidator, orIDomainValidatorConfigProvider. Any future PR that puts a v3.3-only type back on a public constructor will fail this test instead of crashing live plugin load.What was NOT changed
integration-manifest.json#gateway_frameworkremains24.2.0and the README's compatibility line remains '24.2.0 and later'. Both are now accurate post-fix (previously they were aspirational — the plugin would not have loaded on a v3.2 host).Keyfactor.AnyGateway.IAnyCAPluginNuGet pin remains at the v3.3 prerelease so the plugin can still useIDomainValidatorFactoryinternally on hosts that supply it.Verification
net8.0andnet10.0TFMs.test(plugin): behavioral coverage for issue #7 factory-injection path
test(integration): live "DCV off + sync" verification for issue #7
spbsoluble commentedon May 22, 2026
Reporting back from a live re-test on gateway 25.5.0 (ships
Keyfactor.AnyGateway.IAnyCAPluginv3.2.0.0) against the post-fix build that includesce8e02d. Initialize loads cleanly and CA registration works, but the sameTypeLoadExceptiononIDomainValidatorFactorystill fires when the gateway callsInitializeduring enrollment — i.e. PFX/CSR enrolls return HTTP 500 with the same stack trace shape we saw before the fix.Root cause that PR #2 didn't cover
CERTInextCAPlugin.csline 48 still declares the field type as the missing type:.NETJIT resolves the types of every instance field when the containing class is first instantiated — independent of whether any constructor parameter or method signature mentions the type. So the runtime can fully construct the plugin via the public parameterless ctor (the PR #2 fix), but the very first time the host callsInitialize(or any other instance method), the JIT tripsTypeLoadExceptionresolving the field's declared type against the v3.2 assembly. PR #2 fixed constructor-signature loading but did not catch field-type loading.Live evidence
Gateway 25.5.0 pod log (correlation id
F3B7DC2F-E4E3-4554-818C-1C42DC3B1D2C):Note the stack trace lands at the start of
Initialize— the body never executes; the JIT fails before the first instruction runs because resolving the_domainValidatorFactoryfield's declared type fails. This is the well-known "TypeLoadException happens the first time a method touches the class even if the missing type isn't on that line" behavior. The line number is the entry point ofInitialize, not where the actual reference lives.Initialization at CA-registration time works because the gateway's CA registration flow constructs the plugin and calls a different code path that doesn't require JIT-compiling
Initialize(likely a metadata-only inspection path). It's only the enrollment path that requiresInitializeto actually run, which is where the JIT trip happens.Suggested fix
Type the field as
object(matching theSetDomainValidatorFactory(object factory)parameter pattern PR #2 already established) and cast in method bodies on demand:Then anywhere the field is consumed:
The cast happens inside method bodies, which the JIT compiles lazily per-method, so the missing type is only resolved on v3.3+ hosts that supply it. Same pattern that already protects
SetDomainValidatorFactory.The existing regression test (
CERTInextCAPluginPublicSurfaceTests) walks public constructors and would not catch this — it doesn't enumerate field declarations. Worth extending the reflection walk to assert no instance field's declared type referencesIDomainValidatorFactory/IDomainValidator/IDomainValidatorConfigProvidereither.Environment
main(sha256b9f470b07e929f59186b6e7b44ad049d9a32f78987bf5ba0b3210b23e90a0429, 201728 bytes), verified in-pod viasha256sum /app/Extensions/certinext-caplugin/CERTInextCAPlugin.dll.keyfactor.jfrog.io/kyfuseng-release/command/anygateway-rest:25.5.0(shipsKeyfactor.AnyGateway.IAnyCAPluginv3.2.0.0).fix: type field as object + drop dead nested helper (issue #7 follow-up)
spbsoluble commentedon May 22, 2026
Field-type follow-up fix landed in commit
054b2c3on PR #2. Your diagnosis was correct — constructor-signature reflection was the first JIT trip, instance-field declared-type resolution is the second.What changed
CERTInextCAPlugin.cs:48—_domainValidatorFactoryfield re-typed fromvolatile IDomainValidatorFactorytovolatile object. The CLR no longer touches the v3.3-only type during class-load.CERTInextCAPlugin.cs:63— new private propertyDomainValidatorFactoryreturns_domainValidatorFactory as IDomainValidatorFactory. The cast is inside the property body so the JIT defers type resolution to the moment the property actually runs (on a v3.2 host where_domainValidatorFactoryis null, the gating null-check short-circuits before the property is ever invoked, so the missing type is never touched)._domainValidatorFactory != null/== null) stay as direct field reads — comparingobjectto null doesn't touch the v3.3 type at all.DomainValidatorConfigProvider : IDomainValidatorConfigProviderwas deleted. It was declared but never instantiated, and a nested type whose base list references a missing assembly type is itself a class-load hazard.Regression coverage you asked for
CERTInextCAPluginPublicSurfaceTests(the reflection guard from the first pass) gained three new tests beyond the original constructor-signature walk:NoInstanceField_DeclaredTypeReferencesV3Point3OnlyTypes— walks every instance field onCERTInextCAPluginand asserts none is declared asIDomainValidatorFactory/IDomainValidator/IDomainValidatorConfigProvider. This is the exact reflection the JIT does at class-load.NoNestedType_ImplementsV3Point3OnlyInterface— walks nested-type interface lists.NoPublicMethod_SignatureReferencesV3Point3OnlyTypes— walks return and parameter types of public methods.Any future regression in any of those four reflection surfaces will fail at
dotnet testinstead of crashing live plugin load.Verification
net8.0andnet10.0.CERTInextCAPluginPublicSurfaceTestsafter the additions).CERTInext/bin/Release/{net8.0,net10.0}/CERTInextCAPlugin.dll. Thenet8.0is the one to drop into a 25.5.0 host.Ready for another lab re-test when you have a window. If
Initializestill trips, please grab the stack trace + the exact DLL sha256 you're loading and I'll dig further.spbsoluble commentedon May 22, 2026
Confirmed working in the lab on gateway 25.5.0 (IAnyCAPlugin v3.2.0.0) — plugin loads, registers, enrolls, and runs end-to-end. Closing. Thanks for the precise repro + JIT trace; it pointed directly at the field-type leak that the first pass missed.
feat+fix: address issue #8 (diagnostic + rate-limit + env-file)
fix: compliance triage on issue #7 follow-up + issue #8