Every CVESearch/Get API key

CVE-2026-95860

node-opcua: Namespace-Level RBAC Fail-Open - Anonymous Write/Call Bypass via Unbound `DefaultRolePermissions

9.1 CriticalFix available

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.

What does this mean?
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

0.60%Unchanged since Nov 4, 2026
90%50%10%1%

Affected products

Affected packages

Affected packages
PackageAffectedFixed
node-opcua
npm
2.42.0 up to 2.180.0
2.180.0
node-opcua-address-space
npm
2.42.0 up to 2.180.0
2.180.0
node-opcua-server
npm
2.42.0 up to 2.180.0
2.180.0

What each source reports

CVE List Score used9.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 CriticalCVSS: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-1162109.1

node-opcua: Namespace-Level RBAC Fail-Open - Anonymous Write/Call Bypass via Unbound `DefaultRolePermissions

Record
EUVD-2026-116210
CVSS 3.1
9.1 CriticalCVSS: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-jc3f9.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:

  1. 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.
  2. 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 NamespaceMetadata node under Server/Namespaces, so the lookup returns null.

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 null in stock nodesets, so this reset only changes the value's dataType tag; it is not the sole cause, the namespace default is null regardless.

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 CriticalCVSS: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
NVD9.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 CriticalCVSS: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
OSV9.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 CriticalCVSS: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-jc3f9.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:

  1. 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.
  2. 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 NamespaceMetadata node under Server/Namespaces, so the lookup returns null.

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 null in stock nodesets, so this reset only changes the value's dataType tag; it is not the sole cause, the namespace default is null regardless.

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 CriticalCVSS: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

History
RecordedChangedSources
publishedEUVD
affected, referencesOSV
affected, facts, referencesGitHub Advisories, NVD
referencesCVE List, EUVD, OSV
aliases, referencesNVD, OSV
cwes, products, references, severity, titleCVE List
first indexedEUVD

Dates are when Every CVE recorded each change. Upstream dates may differ.

References

This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open