OpcUa:EnabledSecurityProfiles cannot drop None: the binder appends to the pre-filled default list, so the None endpoint is always advertised #879
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by the #800 Windows service gate on windev, 2026-11-12. The bug is in both the SDK 1.5 baseline
efb9197aand SDK 2 master438484fd, so it is not an SDK 2 regression.Symptom: the node was configured with only the secure profiles:
The
OtOpcUaHostservice still advertises aNone/Noneendpoint, with a UserName token policy. GetEndpoints from a probe on the box returnedNone,SignandSignAndEncrypt.docs/security.md§hardening says to "dropNonefromEnabledSecurityProfiles" and shows this exact JSON list, but that has no effect.Cause:
OpcUaApplicationHostOptions.EnabledSecurityProfilesis initialised to the default list[None, Basic256Sha256Sign, Basic256Sha256SignAndEncrypt].ConfigurationBinderappends the configured entries to an existing list instead of replacing it, so the configured profiles are added after the defaults andNoneis never removed. The same happens with the JSON array form in appsettings, because the binder treats both forms the same way.Repro (no server needed; console project referencing the Host project, at master
438484fd):The repro is in
scripts/validation/opcua-windows-service/BindRepro/. The wire evidence is in #800's results (endpoints-advertisedin everychecks-*.json).Impact:
AllowAnonymous=falsestill holds, so a valid key is still required.Fix direction:
BinderOptions/ replace semantics.Fixed on master
a83bf1fa.Fix.
OpcUaApplicationHostOptions.EnabledSecurityProfilesis nownullby default;ResolveEnabledSecurityProfiles()applies the same[None, Basic256Sha256Sign, Basic256Sha256SignAndEncrypt]default only when nothing is configured, so a configured list replaces it. The host consumes the resolved list. Defaults are unchanged.Empty list. An absent key or
[](binds to nothing) means "not configured" and gives the default. A list that is set but empty fails startup validation (OpcUa:EnabledSecurityProfiles must contain at least 1 item(s)), andBuildSecurityPoliciesnow throws on empty input instead of silently falling back to aNoneendpoint. An unknown profile name fails the bind. Docs (security.md,Configuration.md,OpcUaServer.md, CLAUDE.md) updated; the BindRepro now prints only the secure profiles.Anti-pattern sweep. Searched every
{ get; set; }/initcollection property insrc/for a non-empty initializer.EnabledSecurityProfileswas the only one on a bound options class. The rest are empty (PeerApplicationUris,AkkaClusterOptions.SeedNodes/Roles,LdapOptions.GroupToRole,MeshTransportOptions.ClusterPsks,Calculation.RawTags,Sql.Queries) or are not config-bound (ResilienceFormModel.Policies, deployment artifacts). The empty array defaults would still append rather than replace, but that is harmless while the default is empty.Tests.
OpcUaSecurityProfilesBindingTests(8, through the realAddValidatedOptionswiring: indexed, JSON, env forms, explicitNone, default, empty[], set-but-empty, unknown name) andSecurityProfilesReplaceWireTests(2, booted server: secure-only config advertises Sign + SignAndEncrypt and noNone; aNonesession is refused withBadSecurityPolicyRejected; the SignAndEncrypt session works; the default-config case is the control and does admitNone). The old "empty input falls back to None" unit test now asserts the throw. Mutation (re-adding the pre-filled default) fails the wire test and 5 of the 8 binding tests. Existing: OpcUaServer.Tests 1265 pass, validator + binding 29 pass, wire subset (trusted channel, dual endpoint, lockout, namespace) 25 pass, slnx builds 0 warnings.Consumers. No checked-in appsettings, deploy record, or the vd03 upgrade record's deployed-appsettings diff sets
EnabledSecurityProfiles, and no QA record does, so none of DEV vd03 or QA vq03/vq04 is known to rely on the old behaviour. The service environments were not inspected (no ad-hoc WinRM): checkOpcUa__EnabledSecurityProfiles__*before redeploying a box. Only a box that already lists the setting changes behaviour, and for itNonenow really goes.ScadaBridge. It connects with
None+ API key and has no client certificate (ScadaBridge#121), so on any node whereNoneis dropped its link is refused (BadSecurityPolicyRejected). Do not dropNoneon a node ScadaBridge uses until #121 is done. vd03 ScadaBridge has no OPC UA link to OtOpcUa. This fix only makes the setting work; it changes no default.Not deployed. The scadaproj
sister-project-history/otopcua.mdrecord is updated.