CVE-2026-95860
node-opcua: Namespace-Level RBAC Fail-Open - Anonymous Write/Call Bypass via Unbound `DefaultRolePermissions
CVE-2026-95860 is a critical vulnerability (CVSS 9.1) in Node-opcua, published November 3, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.60%, the 49th percentile. Fixed in node-opcua 2.180.0.
- Severity
- 9.1 CriticalCVSS 3.1, CVE List
- Exploit chance
- 0.60%EPSS, 49th percentile
- Exploitation
- None knownNot on CISA’s or ENISA’s lists
- Fixed in
2.180.0node-opcua +2 more
Description
node-opcua is an OPC UA implementation for TypeScript and Node.js. From 2.42.0 until 2.180.0, namespace-level access control configured through setDefaultRolePermissions is not honored by the runtime enforcement path for nodes without explicit per-node RolePermissions. SessionContext.getApplicableRolePermissions reads a missing or null DefaultRolePermissions metadata value instead of the configured namespace field, and the null result is treated permissively. An anonymous network client can consequently write variables with CurrentWrite access or call bound methods when an operator configured a restrictive namespace default and relied on inheritance. Servers with no declared policy, nodes with explicit per-node permissions, and correctly bound programmatic namespace metadata are outside the authorization-bypass case. This issue is fixed in version 2.180.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- None
Exploit likelihood
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
node-opcuanpm | 2.42.0 up to 2.180.0 | 2.180.0 |
node-opcua-address-spacenpm | 2.42.0 up to 2.180.0 | 2.180.0 |
node-opcua-servernpm | 2.42.0 up to 2.180.0 | 2.180.0 |
What each source reports
CVE List Score used 9.1
node-opcua: Namespace-Level RBAC Fail-Open - Anonymous Write/Call Bypass via Unbound `DefaultRolePermissions
node-opcua is an OPC UA implementation for TypeScript and Node.js. From 2.42.0 until 2.180.0, namespace-level access control configured through setDefaultRolePermissions is not honored by the runtime enforcement path for nodes without explicit per-node RolePermissions. SessionContext.getApplicableRolePermissions reads a missing or null DefaultRolePermissions metadata value instead of the configured namespace field, and the null result is treated permissively. An anonymous network client can consequently write variables with CurrentWrite access or call bound methods when an operator configured a restrictive namespace default and relied on inheritance. Servers with no declared policy, nodes with explicit per-node permissions, and correctly bound programmatic namespace metadata are outside the authorization-bypass case. This issue is fixed in version 2.180.0.
- Record
CVE-2026-95860- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-285, CWE-636, CWE-863, CWE-1188
- Updated
- 2026-11-07
EUVDEUVD-2026-116210 9.1
node-opcua: Namespace-Level RBAC Fail-Open - Anonymous Write/Call Bypass via Unbound `DefaultRolePermissions
- Record
EUVD-2026-116210- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Updated
- 2026-11-10
GitHub AdvisoriesGHSA-xggf-m53x-jc3f 9.1
node-opcua: Namespace-Level RBAC Fail-Open - Anonymous Write/Call Bypass via Unbound `DefaultRolePermissions
Summary
An unauthenticated (Anonymous) client can Write protected Variables and Call privileged Methods on any Node that lacks explicit per-node permissions.
This follows the inheritance model defined by OPC UA Part 3, §5.2.9, where nodes without an explicit RolePermissions attribute inherit the namespace DefaultRolePermissions. OPC 10000-3 §4.8.3 mandates a default-deny evaluation (Bad_UserAccessDenied when the operation bits are not set); node-opcua instead grants all permissions.
The root cause is a combination of an authorization fail-open and a disconnect between the configuration API and the runtime enforcement path.
Affected configuration
This advisory covers two cases that are not equally severe:
- No RolePermissions declared anywhere - resolution is permissive. This is node-opcua's documented behaviour for a server with no access control configured, so nothing is being bypassed. Worth fixing as an insecure default (CWE-1188), but it is not the 9.1 bypass. Stock Opc.Ua.NodeSet2.xml ships DefaultRolePermissions with no Value, so a server that declared no policy is NOT exposed.
- A policy is declared and silently ignored - setDefaultRolePermissions() succeeds and does nothing, so an operator who believes they locked the namespace down has not. This is the real bypass and this is what earns CVSS 9.1. It requires that someone declared a namespace default. Everything below describes case 2 unless stated otherwise.
Root Cause Analysis
Bug 1 - getPermissionForRole(null) returns allPermissions (violates §4.8.3 default-deny)
File: packages/node-opcua-address-space/source/session_context.ts:151-153
function getPermissionForRole(rolePermissions: RolePermissionType[] | null, role: NodeId): PermissionFlag {
if (rolePermissions === null) {
return allPermissions; // null means "grant everything" to ANY role, including Anonymous
}
When the applicable policy resolves to null, the function returns allPermissions (Read, Write, Call) to whichever role is checking permissions, including Anonymous. OPC 10000-3 §4.8.3 requires the opposite: "If they are not set the Server returns Bad_UserAccessDenied."
Bug 2 - The namespace default resolves to null on the enforcement path
The runtime enforcement reads the DefaultRolePermissions / DefaultUserRolePermissions Variable nodes under Server/Namespaces/<ns>. These resolve to null in every reachable configuration:
- NodeSet2-loaded namespaces (DI / ADI / vendor) ship the Property without a
Value(null). - Programmatically-created namespaces have no
NamespaceMetadatanode underServer/Namespaces, so the lookup returnsnull.
Additionally, server_engine.ts:947-957 rewrites these nodes to a Null-typed value on every initialize(), with no restore:
for (const ns of namespacesNode.getComponents()) {
const defaultRolePermissions = ns.getChildByName("DefaultRolePermissions") as UAVariable | null;
if (defaultRolePermissions) {
defaultRolePermissions.setValueFromSource({ dataType: DataType.Null });
}
}
Note: the loaded value is already
nullin stock nodesets, so this reset only changes the value'sdataTypetag; it is not the sole cause, the namespace default isnullregardless.
Bug 3 - Two incoherent enforcement paths
There are two separate and disconnected paths to resolve namespace-default permissions.
Path A (private field) - Namespace.setDefaultRolePermissions() writes the policy exclusively to a JavaScript private field in namespace_impl.ts:
public setDefaultRolePermissions(rolePermissions: RolePermissionTypeOptions[] | null): void {
this.defaultRolePermissions = rolePermissions ? coerceRolePermissions(rolePermissions) : undefined;
}
This is the value read by local getters (e.g. BaseNodeImpl.getRolePermissions(inherited=true)).
Path B (UAVariable node) - the…
- Record
GHSA-xggf-m53x-jc3f- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-285, CWE-636, CWE-863, CWE-1188
- Updated
- 2026-11-10
NVD 9.1
node-opcua is an OPC UA implementation for TypeScript and Node.js. From 2.42.0 until 2.180.0, namespace-level access control configured through setDefaultRolePermissions is not honored by the runtime enforcement path for nodes without explicit per-node RolePermissions. SessionContext.getApplicableRolePermissions reads a missing or null DefaultRolePermissions metadata value instead of the configured namespace field, and the null result is treated permissively. An anonymous network client can consequently write variables with CurrentWrite access or call bound methods when an operator configured a restrictive namespace default and relied on inheritance. Servers with no declared policy, nodes with explicit per-node permissions, and correctly bound programmatic namespace metadata are outside the authorization-bypass case. This issue is fixed in version 2.180.0.
- Record
CVE-2026-95860- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-285, CWE-636, CWE-863, CWE-1188
- Updated
- 2026-11-07
OSV 9.1
node-opcua: Namespace-Level RBAC Fail-Open - Anonymous Write/Call Bypass via Unbound `DefaultRolePermissions
node-opcua is an OPC UA implementation for TypeScript and Node.js. From 2.42.0 until 2.180.0, namespace-level access control configured through setDefaultRolePermissions is not honored by the runtime enforcement path for nodes without explicit per-node RolePermissions. SessionContext.getApplicableRolePermissions reads a missing or null DefaultRolePermissions metadata value instead of the configured namespace field, and the null result is treated permissively. An anonymous network client can consequently write variables with CurrentWrite access or call bound methods when an operator configured a restrictive namespace default and relied on inheritance. Servers with no declared policy, nodes with explicit per-node permissions, and correctly bound programmatic namespace metadata are outside the authorization-bypass case. This issue is fixed in version 2.180.0.
- Record
CVE-2026-95860- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-1188, CWE-285, CWE-636, CWE-863
- Updated
- 2026-11-11
OSVGHSA-xggf-m53x-jc3f 9.1
node-opcua: Namespace-Level RBAC Fail-Open - Anonymous Write/Call Bypass via Unbound `DefaultRolePermissions
Summary
An unauthenticated (Anonymous) client can Write protected Variables and Call privileged Methods on any Node that lacks explicit per-node permissions.
This follows the inheritance model defined by OPC UA Part 3, §5.2.9, where nodes without an explicit RolePermissions attribute inherit the namespace DefaultRolePermissions. OPC 10000-3 §4.8.3 mandates a default-deny evaluation (Bad_UserAccessDenied when the operation bits are not set); node-opcua instead grants all permissions.
The root cause is a combination of an authorization fail-open and a disconnect between the configuration API and the runtime enforcement path.
Affected configuration
This advisory covers two cases that are not equally severe:
- No RolePermissions declared anywhere - resolution is permissive. This is node-opcua's documented behaviour for a server with no access control configured, so nothing is being bypassed. Worth fixing as an insecure default (CWE-1188), but it is not the 9.1 bypass. Stock Opc.Ua.NodeSet2.xml ships DefaultRolePermissions with no Value, so a server that declared no policy is NOT exposed.
- A policy is declared and silently ignored - setDefaultRolePermissions() succeeds and does nothing, so an operator who believes they locked the namespace down has not. This is the real bypass and this is what earns CVSS 9.1. It requires that someone declared a namespace default. Everything below describes case 2 unless stated otherwise.
Root Cause Analysis
Bug 1 - getPermissionForRole(null) returns allPermissions (violates §4.8.3 default-deny)
File: packages/node-opcua-address-space/source/session_context.ts:151-153
function getPermissionForRole(rolePermissions: RolePermissionType[] | null, role: NodeId): PermissionFlag {
if (rolePermissions === null) {
return allPermissions; // null means "grant everything" to ANY role, including Anonymous
}
When the applicable policy resolves to null, the function returns allPermissions (Read, Write, Call) to whichever role is checking permissions, including Anonymous. OPC 10000-3 §4.8.3 requires the opposite: "If they are not set the Server returns Bad_UserAccessDenied."
Bug 2 - The namespace default resolves to null on the enforcement path
The runtime enforcement reads the DefaultRolePermissions / DefaultUserRolePermissions Variable nodes under Server/Namespaces/<ns>. These resolve to null in every reachable configuration:
- NodeSet2-loaded namespaces (DI / ADI / vendor) ship the Property without a
Value(null). - Programmatically-created namespaces have no
NamespaceMetadatanode underServer/Namespaces, so the lookup returnsnull.
Additionally, server_engine.ts:947-957 rewrites these nodes to a Null-typed value on every initialize(), with no restore:
for (const ns of namespacesNode.getComponents()) {
const defaultRolePermissions = ns.getChildByName("DefaultRolePermissions") as UAVariable | null;
if (defaultRolePermissions) {
defaultRolePermissions.setValueFromSource({ dataType: DataType.Null });
}
}
Note: the loaded value is already
nullin stock nodesets, so this reset only changes the value'sdataTypetag; it is not the sole cause, the namespace default isnullregardless.
Bug 3 - Two incoherent enforcement paths
There are two separate and disconnected paths to resolve namespace-default permissions.
Path A (private field) - Namespace.setDefaultRolePermissions() writes the policy exclusively to a JavaScript private field in namespace_impl.ts:
public setDefaultRolePermissions(rolePermissions: RolePermissionTypeOptions[] | null): void {
this.defaultRolePermissions = rolePermissions ? coerceRolePermissions(rolePermissions) : undefined;
}
This is the value read by local getters (e.g. BaseNodeImpl.getRolePermissions(inherited=true)).
Path B (UAVariable node) - the…
- Record
GHSA-xggf-m53x-jc3f- CVSS 3.1
- 9.1 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N - Weakness
- CWE-1188, CWE-285, CWE-636, CWE-863
- Updated
- 2026-11-10
History
| Recorded | Changed | Sources |
|---|---|---|
| published | EUVD | |
| affected, references | OSV | |
| affected, facts, references | GitHub Advisories, NVD | |
| references | CVE List, EUVD, OSV | |
| aliases, references | NVD, OSV | |
| cwes, products, references, severity, title | CVE List | |
| first indexed | EUVD |
Dates are when Every CVE recorded each change. Upstream dates may differ.
References
github.com/node-opcua/node-opcua/security/advisories/GHSA-xggf-m53x-jc3f
github.com/node-opcua/node-opcua/pull/1580
github.com/node-opcua/node-opcua/commit/0261e873900800983c622e01120f484dc6063698
github.com/node-opcua/node-opcua/releases/tag/v2.180.0
nvd.nist.gov/vuln/detail/CVE-2026-95860
github.com/advisories/GHSA-xggf-m53x-jc3f
github.com/CVEProject/cvelistV5/tree/main/cves/2026/95xxx/CVE-2026-95860.json
github.com/node-opcua/node-opcua
Left out here: the rest of 2 source descriptions. The JSON record has them.