Common Weakness Enumeration

CWE-668

Discouraged

Exposure of Resource to Wrong Sphere

Abstraction: Class · Status: Draft

The product exposes a resource to the wrong control sphere, providing unintended actors with inappropriate access to the resource.

1251 vulnerabilities reference this CWE, most recent first.

GHSA-9852-HR94-HF7P

Vulnerability from github – Published: 2022-04-12 00:00 – Updated: 2022-04-19 00:01
VLAI
Details

Information exposure vulnerability in ril property setting prior to SMR April-2022 Release 1 allows access to EF_RUIMID value without permission.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-27822"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-04-11T20:15:00Z",
    "severity": "MODERATE"
  },
  "details": "Information exposure vulnerability in ril property setting prior to SMR April-2022 Release 1 allows access to EF_RUIMID value without permission.",
  "id": "GHSA-9852-hr94-hf7p",
  "modified": "2022-04-19T00:01:12Z",
  "published": "2022-04-12T00:00:25Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-27822"
    },
    {
      "type": "WEB",
      "url": "https://security.samsungmobile.com/securityUpdate.smsb?year=2022\u0026month=4"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-98M4-M2C3-QXGQ

Vulnerability from github – Published: 2022-05-24 17:01 – Updated: 2022-12-06 21:56
VLAI
Summary
Jenkins JIRA Plugin allows users to select and use credentials with System scope
Details

Jenkins JIRA Plugin 3.0.10 and earlier does not declare the correct (folder) scope for per-folder Jira site definitions, allowing users to select and use credentials with System scope. Jira Plugin 3.0.11 defines the appropriate folder context for credential lookup. As a side effect, existing per-folder Jira sites may lose access to already configured System-scoped credentials, as if no credential was specified in the first place.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.0.10"
      },
      "package": {
        "ecosystem": "Maven",
        "name": "org.jenkins-ci.plugins:jira"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.0.11"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2019-16541"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2022-12-06T21:56:30Z",
    "nvd_published_at": "2019-11-21T15:15:00Z",
    "severity": "MODERATE"
  },
  "details": "Jenkins JIRA Plugin 3.0.10 and earlier does not declare the correct (folder) scope for per-folder Jira site definitions, allowing users to select and use credentials with System scope. Jira Plugin 3.0.11 defines the appropriate folder context for credential lookup. As a side effect, existing per-folder Jira sites may lose access to already configured System-scoped credentials, as if no credential was specified in the first place.",
  "id": "GHSA-98m4-m2c3-qxgq",
  "modified": "2022-12-06T21:56:30Z",
  "published": "2022-05-24T17:01:40Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-16541"
    },
    {
      "type": "WEB",
      "url": "https://github.com/jenkinsci/jira-plugin/commit/3214a54b6871d82cb34a26949aad93b0fa78d1a8"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/jenkinsci/jira-plugin"
    },
    {
      "type": "WEB",
      "url": "https://jenkins.io/security/advisory/2019-11-21/#SECURITY-1106"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2019/11/21/1"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Jenkins JIRA Plugin allows users to select and use credentials with System scope"
}

GHSA-99P4-7RF3-P26Q

Vulnerability from github – Published: 2023-01-27 15:30 – Updated: 2023-02-07 21:30
VLAI
Details

An information disclosure vulnerability in Totolink A830R V4.1.2cu.5182 allows attackers to obtain the root password via a brute-force attack.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-48067"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668",
      "CWE-798"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2023-01-27T15:15:00Z",
    "severity": "MODERATE"
  },
  "details": "An information disclosure vulnerability in Totolink A830R V4.1.2cu.5182 allows attackers to obtain the root password via a brute-force attack.",
  "id": "GHSA-99p4-7rf3-p26q",
  "modified": "2023-02-07T21:30:21Z",
  "published": "2023-01-27T15:30:31Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-48067"
    },
    {
      "type": "WEB",
      "url": "https://befitting-vinca-933.notion.site/Totolink-A830R-V4-1-2cu-5182-Sensitive-Information-Disclosure-567f4a17d5cc401b97e5c3e61aae8ca0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-9F3W-8VW8-XR57

Vulnerability from github – Published: 2022-01-12 00:00 – Updated: 2024-11-14 21:31
VLAI
Details

Windows GDI Information Disclosure Vulnerability.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-21904"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-01-11T21:15:00Z",
    "severity": "HIGH"
  },
  "details": "Windows GDI Information Disclosure Vulnerability.",
  "id": "GHSA-9f3w-8vw8-xr57",
  "modified": "2024-11-14T21:31:50Z",
  "published": "2022-01-12T00:00:42Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-21904"
    },
    {
      "type": "WEB",
      "url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-21904"
    },
    {
      "type": "WEB",
      "url": "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2022-21904"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-9F9M-8VQ9-GWH7

Vulnerability from github – Published: 2022-05-24 19:20 – Updated: 2022-07-13 00:01
VLAI
Details

Azure RTOS Information Disclosure Vulnerability This CVE ID is unique from CVE-2021-26444, CVE-2021-42301.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2021-42323"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2021-11-10T01:19:00Z",
    "severity": "LOW"
  },
  "details": "Azure RTOS Information Disclosure Vulnerability This CVE ID is unique from CVE-2021-26444, CVE-2021-42301.",
  "id": "GHSA-9f9m-8vq9-gwh7",
  "modified": "2022-07-13T00:01:42Z",
  "published": "2022-05-24T19:20:20Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-42323"
    },
    {
      "type": "WEB",
      "url": "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2021-42323"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-9FHQ-8W23-F2MG

Vulnerability from github – Published: 2021-12-16 00:01 – Updated: 2023-08-08 15:31
VLAI
Details

Microsoft Defender for IoT Information Disclosure Vulnerability

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2021-43888"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2021-12-15T15:15:00Z",
    "severity": "HIGH"
  },
  "details": "Microsoft Defender for IoT Information Disclosure Vulnerability",
  "id": "GHSA-9fhq-8w23-f2mg",
  "modified": "2023-08-08T15:31:24Z",
  "published": "2021-12-16T00:01:44Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-43888"
    },
    {
      "type": "WEB",
      "url": "https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2021-43888"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-9G8X-92Q2-P28F

Vulnerability from github – Published: 2026-05-29 18:20 – Updated: 2026-06-12 19:30
VLAI
Summary
NodeVM observability builtins leak host process and HTTP request data
Details

Summary

NodeVM exposes some process-wide observability builtins when they are allowed through require.builtin.

The following builtins are not blocked by the dangerous builtin denylist:

diagnostics_channel
async_hooks
perf_hooks

These modules are process-wide, not sandbox-local. Sandboxed code can use them to observe host application data across the vm2 boundary.

Note: It is a host data exposure issue. The impact depends on whether the host application allows these builtins and uses HTTP, async request context, diagnostics channels, or performance marks in the same process.

Details

Non-denied builtins are exposed to the sandbox through lib/builtin.js:

builtins.set(key, special ? special : vm => vm.readonly(hostRequire(key)));

diagnostics_channel, async_hooks, and perf_hooks are not denied. These modules expose host process state rather than sandbox-local state.

Confirmed examples:

  1. diagnostics_channel lets sandboxed code subscribe to Node.js HTTP diagnostic channels such as http.server.request.start. The sandbox receives host HTTP request objects and can read headers such as Authorization or session tokens.
  2. async_hooks.executionAsyncResource() lets sandboxed code read the current host AsyncResource. If the host stores request/user data on that resource, the sandbox can read it.
  3. perf_hooks.performance.getEntriesByType('mark') lets sandboxed code read host performance timeline entries.

PoC

Run from the vm2 repository root:

node poc/observability-builtins-info-leak.js

observability-builtins-info-leak.js

The PoC uses only the specific builtin being tested in each section.

It confirms:

diagnostics_channel: sandbox reads host HTTP request headers
async_hooks: sandbox reads host AsyncResource data
perf_hooks: sandbox reads host performance mark names

Example impact from the PoC:

authorization: Bearer HOST_HTTP_SECRET_...
x-session-token: HOST_HTTP_SECRET_...

These values are sent to a host HTTP server, but the sandbox reads them through diagnostics_channel.

Screenshot 2026-05-10 at 1 13 20 PM

Impact

An attacker who can run untrusted JavaScript inside NodeVM with affected builtin settings can observe data from the host process.

In a real application, this may expose HTTP request headers, authorization tokens, session tokens, request context values, user identifiers, or other sensitive diagnostics data from the host application or from other users.

Suggested fix

Treat process-wide observability modules as dangerous builtins for untrusted sandboxes.

At minimum, consider blocking:

diagnostics_channel
async_hooks
perf_hooks

These modules are not sandbox-local and can expose host process state across the vm2 boundary.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.11.3"
      },
      "package": {
        "ecosystem": "npm",
        "name": "vm2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.11.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-47141"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-29T18:20:45Z",
    "nvd_published_at": "2026-06-12T15:16:28Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\n`NodeVM` exposes some process-wide observability builtins when they are allowed through `require.builtin`.\n\nThe following builtins are not blocked by the dangerous builtin denylist:\n\n```text\ndiagnostics_channel\nasync_hooks\nperf_hooks\n```\n\nThese modules are process-wide, not sandbox-local. Sandboxed code can use them to observe host application data across the vm2 boundary.\n\n**Note**: It is a host data exposure issue. The impact depends on whether the host application allows these builtins and uses HTTP, async request context, diagnostics channels, or performance marks in the same process.\n\n## Details\n\nNon-denied builtins are exposed to the sandbox through `lib/builtin.js`:\n\n```js\nbuiltins.set(key, special ? special : vm =\u003e vm.readonly(hostRequire(key)));\n```\n\n`diagnostics_channel`, `async_hooks`, and `perf_hooks` are not denied. These modules expose host process state rather than sandbox-local state.\n\nConfirmed examples:\n\n1. `diagnostics_channel` lets sandboxed code subscribe to Node.js HTTP diagnostic channels such as `http.server.request.start`. The sandbox receives host HTTP request objects and can read headers such as `Authorization` or session tokens.\n2. `async_hooks.executionAsyncResource()` lets sandboxed code read the current host `AsyncResource`. If the host stores request/user data on that resource, the sandbox can read it.\n3. `perf_hooks.performance.getEntriesByType(\u0027mark\u0027)` lets sandboxed code read host performance timeline entries.\n\n## PoC\n\nRun from the vm2 repository root:\n\n```bash\nnode poc/observability-builtins-info-leak.js\n```\n[observability-builtins-info-leak.js](https://github.com/user-attachments/files/27571259/observability-builtins-info-leak.js)\n\nThe PoC uses only the specific builtin being tested in each section.\n\nIt confirms:\n\n```text\ndiagnostics_channel: sandbox reads host HTTP request headers\nasync_hooks: sandbox reads host AsyncResource data\nperf_hooks: sandbox reads host performance mark names\n```\n\nExample impact from the PoC:\n\n```text\nauthorization: Bearer HOST_HTTP_SECRET_...\nx-session-token: HOST_HTTP_SECRET_...\n```\n\nThese values are sent to a host HTTP server, but the sandbox reads them through `diagnostics_channel`.\n\n\u003cimg width=\"997\" height=\"566\" alt=\"Screenshot 2026-05-10 at 1 13 20\u202fPM\" src=\"https://github.com/user-attachments/assets/36a7d600-8b53-4bfe-ab06-4e6dcfad5015\" /\u003e\n\n## Impact\n\nAn attacker who can run untrusted JavaScript inside `NodeVM` with affected builtin settings can observe data from the host process.\n\nIn a real application, this may expose HTTP request headers, authorization tokens, session tokens, request context values, user identifiers, or other sensitive diagnostics data from the host application or from other users.\n\n## Suggested fix\n\nTreat process-wide observability modules as dangerous builtins for untrusted sandboxes.\n\nAt minimum, consider blocking:\n\n```text\ndiagnostics_channel\nasync_hooks\nperf_hooks\n```\n\nThese modules are not sandbox-local and can expose host process state across the vm2 boundary.",
  "id": "GHSA-9g8x-92q2-p28f",
  "modified": "2026-06-12T19:30:13Z",
  "published": "2026-05-29T18:20:45Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/security/advisories/GHSA-9g8x-92q2-p28f"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-47141"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/commit/e1c48fce05189f48e71efbd32af0754efa4066bb"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/patriksimek/vm2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/patriksimek/vm2/releases/tag/v3.11.4"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:L/SI:N/SA:N/E:U",
      "type": "CVSS_V4"
    }
  ],
  "summary": "NodeVM observability builtins leak host process and HTTP request data"
}

GHSA-9GMC-P2C5-WM4V

Vulnerability from github – Published: 2022-01-15 00:01 – Updated: 2022-01-16 00:00
VLAI
Details

In StatusBar.java, there is a possible disclosure of notification content on the lockscreen due to a logic error in the code. This could lead to local information disclosure with no additional execution privileges needed. User interaction is not needed for exploitation.Product: AndroidVersions: Android-10 Android-11Android ID: A-189575031

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2021-39628"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-01-14T20:15:00Z",
    "severity": "LOW"
  },
  "details": "In StatusBar.java, there is a possible disclosure of notification content on the lockscreen due to a logic error in the code. This could lead to local information disclosure with no additional execution privileges needed. User interaction is not needed for exploitation.Product: AndroidVersions: Android-10 Android-11Android ID: A-189575031",
  "id": "GHSA-9gmc-p2c5-wm4v",
  "modified": "2022-01-16T00:00:49Z",
  "published": "2022-01-15T00:01:23Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-39628"
    },
    {
      "type": "WEB",
      "url": "https://source.android.com/security/bulletin/2022-01-01"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-9H7C-FM35-PJFG

Vulnerability from github – Published: 2021-12-15 00:00 – Updated: 2021-12-21 00:01
VLAI
Details

An issue was discovered in Listary through 6. An attacker can create a \.\pipe\Listary.listaryService named pipe and wait for a privileged user to open a session on the Listary installed host. Listary will automatically access the named pipe and the attacker will be able to duplicate the victim's token to impersonate him. This exploit is valid in certain Windows versions (Microsoft has patched the issue in later Windows 10 builds).

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2021-41065"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-668"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2021-12-14T16:15:00Z",
    "severity": "HIGH"
  },
  "details": "An issue was discovered in Listary through 6. An attacker can create a \\\\.\\pipe\\Listary.listaryService named pipe and wait for a privileged user to open a session on the Listary installed host. Listary will automatically access the named pipe and the attacker will be able to duplicate the victim\u0027s token to impersonate him. This exploit is valid in certain Windows versions (Microsoft has patched the issue in later Windows 10 builds).",
  "id": "GHSA-9h7c-fm35-pjfg",
  "modified": "2021-12-21T00:01:29Z",
  "published": "2021-12-15T00:00:44Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-41065"
    },
    {
      "type": "WEB",
      "url": "https://medium.com/@tomerp_77017/exploiting-listary-searching-your-way-to-system-privileges-8175af676c3e"
    },
    {
      "type": "WEB",
      "url": "https://www.listary.com/download"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-9H85-G7W3-RH49

Vulnerability from github – Published: 2026-07-15 22:51 – Updated: 2026-07-15 22:51
VLAI
Summary
ViewComponent: Reused Component Instances Retain Stale Render Context
Details

Reused Component Instances Retain Stale Render Context

Summary

ViewComponent::Base instances retain multiple render-scoped objects across calls to render_in. If the same component, collection, or spacer component instance is reused across requests, users, tenants, or threads, later renders can use stale helpers, controller, request, view_flow, format/variant details, and slot child context from an earlier render.

This can cause authorization-aware components to render privileged UI for a lower-privileged user, generate links using a stale Host header, leak slot/helper state, and mix request context under concurrent rendering.

Severity

The PoC demonstrates cross-user authorization impact in a realistic downstream application pattern. If the receiving program accepts downstream cross-user authorization impact as a scope-changing impact for a framework vulnerability, an alternative High score can be assigned:

Alternative CVSS: 8.2 Alternative vector: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:N

Affected Code

Validated against:

  • Repository commit: eea79445
  • Ruby: 3.4.9

Relevant locations:

  • lib/view_component/base.rb
  • render_in
  • controller
  • helpers
  • __vc_request
  • lib/view_component/slot.rb
  • Slot#to_s
  • lib/view_component/slotable.rb
  • slot storage in @__vc_set_slots
  • lib/view_component/collection.rb
  • child component memoization and spacer rendering

Key retained state:

@view_context = view_context
self.__vc_original_view_context ||= view_context
@lookup_context ||= view_context.lookup_context
@view_flow ||= view_context.view_flow
@__vc_requested_details ||= @lookup_context.vc_requested_details
@__vc_controller ||= view_context.controller
@__vc_helpers ||= __vc_original_view_context || controller.view_context
@__vc_request ||= controller.request if controller.respond_to?(:request)

Slot children also inherit the parent original view context:

@__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context

Collections memoize child component instances:

return @components if defined? @components

Root Cause

Component instances are mutable render objects. render_in updates some per-render fields, but many request-scoped values are memoized using ||= or stored for later slot/collection rendering.

There is no runtime guard preventing a component instance from being rendered multiple times under different view contexts, and there is no full reset of render-scoped state at the start of each render.

Maintainer discussion in prior PRs notes that component instances should not be shared between renders, but the current runtime does not enforce this invariant.

Proof of Concept

The following PoC demonstrates four independent effects:

  • stale authorization gate
  • stale Host/request data in generated absolute URLs
  • stale slot child context
  • cross-thread context mixing

Run from the repository root:

$LOAD_PATH.unshift File.expand_path("lib", Dir.pwd)
require "action_controller/railtie"
require "rack/mock"
require "view_component/base"

class ReusePocController < ActionController::Base
  helper_method :current_user, :admin?
  attr_accessor :current_user, :role
  def admin? = role == :admin
end

routes = ActionDispatch::Routing::RouteSet.new
routes.draw { get "/accounts/:id", to: "accounts#show" }
ReusePocController.include routes.url_helpers

class AdminPanelComponent < ViewComponent::Base
  def render? = helpers.admin?

  def call
    href = helpers.url_for(controller: "accounts", action: "show", id: 42, only_path: false)
    "ADMIN user=#{helpers.current_user};host=#{request.host};href=#{href}".html_safe
  end
end

class UrlOnlyComponent < ViewComponent::Base
  def call
    href = helpers.url_for(controller: "accounts", action: "show", id: 42, only_path: false)
    "user=#{helpers.current_user};host=#{request.host};href=#{href}".html_safe
  end
end

class SlotChildComponent < ViewComponent::Base
  def call = "child_user=#{helpers.current_user};child_path=#{request.path}".html_safe
end

class SlotParentComponent < ViewComponent::Base
  renders_one :child, SlotChildComponent
  def call = "parent_user=#{helpers.current_user};parent_path=#{request.path};".html_safe + child.to_s
end

class RaceComponent < ViewComponent::Base
  def before_render = sleep 0.05
  def call = "#{helpers.current_user}@#{request.path}".html_safe
end

def vc(user:, role:, path:, host: "app.example")
  c = ReusePocController.new
  c.current_user = user
  c.role = role
  c.set_request!(ActionDispatch::Request.new(Rack::MockRequest.env_for(path, "HTTP_HOST" => host)))
  c.set_response!(ActionDispatch::Response.new)
  c.view_context
end

admin_vc = vc(user: "alice", role: :admin, path: "/admin", host: "admin.example")
guest_vc = vc(user: "bob", role: :guest, path: "/guest", host: "app.example")

panel = AdminPanelComponent.new
puts "auth_admin_first=#{panel.render_in(admin_vc)}"
puts "auth_guest_reused=#{panel.render_in(guest_vc)}"
puts "auth_guest_fresh=#{AdminPanelComponent.new.render_in(guest_vc).inspect}"

url = UrlOnlyComponent.new
puts "host_attacker_prime=#{url.render_in(vc(user: "attacker", role: :guest, path: "/prime", host: "evil.example"))}"
puts "host_victim_reused=#{url.render_in(vc(user: "victim", role: :guest, path: "/account", host: "app.example"))}"
puts "host_victim_fresh=#{UrlOnlyComponent.new.render_in(vc(user: "victim", role: :guest, path: "/account", host: "app.example"))}"

parent = SlotParentComponent.new
puts "slot_admin_first=#{parent.render_in(admin_vc) { |p| p.with_child }}"
puts "slot_guest_reused=#{parent.render_in(guest_vc) { |p| p.with_child }}"
puts "slot_guest_fresh=#{SlotParentComponent.new.render_in(guest_vc) { |p| p.with_child }}"

race = RaceComponent.new
q = Queue.new
t1 = Thread.new { q << [:admin, race.render_in(vc(user: "admin", role: :admin, path: "/admin"))] }
t2 = Thread.new { q << [:guest, race.render_in(vc(user: "guest", role: :guest, path: "/guest"))] }
t1.join
t2.join
results = 2.times.map { q.pop }.to_h
puts "race_admin_thread=#{results[:admin]}"
puts "race_guest_thread=#{results[:guest]}"

Observed output:

auth_admin_first=ADMIN user=alice;host=admin.example;href=http://admin.example/accounts/42
auth_guest_reused=ADMIN user=alice;host=admin.example;href=http://admin.example/accounts/42
auth_guest_fresh=""

host_attacker_prime=user=attacker;host=evil.example;href=http://evil.example/accounts/42
host_victim_reused=user=attacker;host=evil.example;href=http://evil.example/accounts/42
host_victim_fresh=user=victim;host=app.example;href=http://app.example/accounts/42

slot_admin_first=parent_user=alice;parent_path=/admin;child_user=alice;child_path=/admin
slot_guest_reused=parent_user=alice;parent_path=/admin;child_user=alice;child_path=/guest
slot_guest_fresh=parent_user=bob;parent_path=/guest;child_user=bob;child_path=/guest

race_admin_thread=admin@/guest
race_guest_thread=admin@/guest

Authorization-Impact PoC

The following PoC models a realistic downstream application pattern: a shared component registry caches component objects instead of caching component classes, factories, or rendered strings. An admin request primes the cached toolbar component. A later guest request renders the same cached object.

The component uses render? as an authorization-aware visibility gate and emits a representative privileged action link.

$LOAD_PATH.unshift File.expand_path("lib", Dir.pwd)
require "action_controller/railtie"
require "rack/mock"
require "view_component/base"

module SharedComponentRegistry
  def self.admin_toolbar
    @admin_toolbar ||= AdminToolbarComponent.new
  end

  def self.reset!
    remove_instance_variable(:@admin_toolbar) if defined?(@admin_toolbar)
  end
end

User = Struct.new(:id, :role, keyword_init: true) do
  def admin? = role == :admin
end

class AppController < ActionController::Base
  helper_method :current_user, :admin?
  attr_accessor :current_user

  def admin?
    current_user&.admin?
  end
end

routes = ActionDispatch::Routing::RouteSet.new
routes.draw do
  get "/admin/users/:id/impersonate", to: "admin/users#impersonate", as: :impersonate_admin_user
end
AppController.include routes.url_helpers

class AdminToolbarComponent < ViewComponent::Base
  def render?
    helpers.admin?
  end

  def call
    helpers.link_to(
      "Impersonate user 42",
      helpers.impersonate_admin_user_url(42, host: request.host),
      data: { turbo_method: :post }
    )
  end
end

class DashboardController < AppController
  def render_dashboard_with_shared_component
    render_to_string(inline: '<main><h1>Dashboard</h1><%= render SharedComponentRegistry.admin_toolbar %></main>')
  end

  def render_dashboard_with_fresh_component
    render_to_string(inline: '<main><h1>Dashboard</h1><%= render AdminToolbarComponent.new %></main>')
  end
end

def controller_for(user:, host:, path: "/dashboard")
  c = DashboardController.new
  c.current_user = user
  c.set_request!(ActionDispatch::Request.new(Rack::MockRequest.env_for(path, "HTTP_HOST" => host)))
  c.set_response!(ActionDispatch::Response.new)
  c
end

SharedComponentRegistry.reset!
admin = User.new(id: 1, role: :admin)
guest = User.new(id: 2, role: :guest)

admin_response = controller_for(user: admin, host: "admin.example").render_dashboard_with_shared_component
guest_reused_response = controller_for(user: guest, host: "app.example").render_dashboard_with_shared_component
guest_fresh_response = controller_for(user: guest, host: "app.example").render_dashboard_with_fresh_component

puts "admin_shared_contains_admin_link=#{admin_response.include?('/admin/users/42/impersonate')}"
puts "guest_reused_contains_admin_link=#{guest_reused_response.include?('/admin/users/42/impersonate')}"
puts "guest_fresh_contains_admin_link=#{guest_fresh_response.include?('/admin/users/42/impersonate')}"
puts "guest_reused_contains_admin_host=#{guest_reused_response.include?('http://admin.example/admin/users/42/impersonate')}"
puts "guest_reused_response=#{guest_reused_response.gsub(/\s+/, ' ').strip}"
puts "guest_fresh_response=#{guest_fresh_response.gsub(/\s+/, ' ').strip.inspect}"

Observed output:

admin_shared_contains_admin_link=true
guest_reused_contains_admin_link=true
guest_fresh_contains_admin_link=false
guest_reused_contains_admin_host=true
guest_reused_response=<main><h1>Dashboard</h1><a data-turbo-method="post" href="http://admin.example/admin/users/42/impersonate">Impersonate user 42</a></main>
guest_fresh_response="<main><h1>Dashboard</h1></main>"

This confirms a cross-user authorization impact in a realistic pattern: a guest receives privileged UI that a fresh component correctly suppresses. It also confirms stale request and Host context in the generated privileged URL.

Exploit Scenario

A downstream app stores component instances in a constant, singleton service, memoized helper, cache object, or shared collection builder to avoid allocation. An attacker or lower-privileged user later triggers rendering of that same object.

Potential real-world examples:

  • A navigation/sidebar component checks helpers.admin? in render?.
  • A tenant switcher uses request.host or current_user.account.
  • A component emits absolute URLs or signed action links.
  • A table uses slot child components that rely on helper/request state.
  • A global UI registry stores instantiated spacer or child components.

In these cases, a component first rendered under an admin or attacker-controlled request can affect later renders for other users.

Impact

Confirmed impact classes:

  • stale privileged UI rendering
  • stale user identity through helpers
  • stale Host/request data in generated absolute URLs
  • slot child context inheritance
  • cross-thread context corruption
  • stale format/variant template selection
  • stale view_flow / content_for writes
  • collection and spacer component context leakage

This can chain into privilege escalation if an application relies on UI visibility as an authorization boundary. It can also leak signed links, tenant-specific URLs, admin actions, or user-specific data.

Preconditions

  • The same component, collection, slot, or spacer component instance is reused across render contexts.
  • The component reads request-scoped or user-scoped APIs such as helpers, controller, request, URL helpers, render?, before_render, slots, variants, formats, or content_for.
  • Higher impact when the shared object crosses users, tenants, roles, or threads.

Normal per-request usage such as render(MyComponent.new(...)) is not affected.

Chaining Potential

This issue can chain with:

  • UI-only authorization checks
  • signed admin links embedded in components
  • Host header poisoning
  • multi-tenant routing based on host/subdomain
  • shared component registries
  • fragment/component caching patterns that cache objects rather than rendered strings
  • concurrent Rails servers such as Puma

The framework alone does not directly prove account takeover, but downstream applications can reach high impact if stale component output exposes privileged action links or bypasses server-side authorization assumptions.

Remediation

The safest fix is to make component and collection instances one-shot renderables.

Recommended options:

  1. Add a runtime guard in render_in that raises or warns when the same component instance is rendered again with a different view_context.
  2. Reset render-scoped ivars at the beginning of every render, including:
  3. __vc_original_view_context
  4. @lookup_context
  5. @view_flow
  6. @__vc_requested_details
  7. @__vc_controller
  8. @__vc_helpers
  9. @__vc_request
  10. Rebuild ViewComponent::Collection child component instances per render or document/enforce collections as one-shot.
  11. Avoid accepting a reusable instantiated spacer_component, or reset/clone it before rendering.
  12. Add thread-safety tests for concurrent rendering of a shared instance.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "RubyGems",
        "name": "view_component"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "4.0.0"
            },
            {
              "fixed": "4.12.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-54497"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-362",
      "CWE-488",
      "CWE-668"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-15T22:51:33Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "# Reused Component Instances Retain Stale Render Context\n\n## Summary\n\n`ViewComponent::Base` instances retain multiple render-scoped objects across calls to `render_in`. If the same component, collection, or spacer component instance is reused across requests, users, tenants, or threads, later renders can use stale `helpers`, `controller`, `request`, `view_flow`, format/variant details, and slot child context from an earlier render.\n\nThis can cause authorization-aware components to render privileged UI for a lower-privileged user, generate links using a stale Host header, leak slot/helper state, and mix request context under concurrent rendering.\n\n## Severity\n\nThe PoC demonstrates cross-user authorization impact in a realistic downstream application pattern.\nIf the receiving program accepts downstream cross-user authorization impact as a scope-changing impact for a framework vulnerability, an alternative High score can be assigned:\n\nAlternative CVSS: 8.2\nAlternative vector: `CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:N`\n\n## Affected Code\n\nValidated against:\n\n- Repository commit: `eea79445`\n- Ruby: `3.4.9`\n\nRelevant locations:\n\n- `lib/view_component/base.rb`\n  - `render_in`\n  - `controller`\n  - `helpers`\n  - `__vc_request`\n- `lib/view_component/slot.rb`\n  - `Slot#to_s`\n- `lib/view_component/slotable.rb`\n  - slot storage in `@__vc_set_slots`\n- `lib/view_component/collection.rb`\n  - child component memoization and spacer rendering\n\nKey retained state:\n\n```ruby\n@view_context = view_context\nself.__vc_original_view_context ||= view_context\n@lookup_context ||= view_context.lookup_context\n@view_flow ||= view_context.view_flow\n@__vc_requested_details ||= @lookup_context.vc_requested_details\n```\n\n```ruby\n@__vc_controller ||= view_context.controller\n@__vc_helpers ||= __vc_original_view_context || controller.view_context\n@__vc_request ||= controller.request if controller.respond_to?(:request)\n```\n\nSlot children also inherit the parent original view context:\n\n```ruby\n@__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context\n```\n\nCollections memoize child component instances:\n\n```ruby\nreturn @components if defined? @components\n```\n\n## Root Cause\n\nComponent instances are mutable render objects. `render_in` updates some per-render fields, but many request-scoped values are memoized using `||=` or stored for later slot/collection rendering.\n\nThere is no runtime guard preventing a component instance from being rendered multiple times under different view contexts, and there is no full reset of render-scoped state at the start of each render.\n\nMaintainer discussion in prior PRs notes that component instances should not be shared between renders, but the current runtime does not enforce this invariant.\n\n## Proof of Concept\n\nThe following PoC demonstrates four independent effects:\n\n- stale authorization gate\n- stale Host/request data in generated absolute URLs\n- stale slot child context\n- cross-thread context mixing\n\nRun from the repository root:\n\n```ruby\n$LOAD_PATH.unshift File.expand_path(\"lib\", Dir.pwd)\nrequire \"action_controller/railtie\"\nrequire \"rack/mock\"\nrequire \"view_component/base\"\n\nclass ReusePocController \u003c ActionController::Base\n  helper_method :current_user, :admin?\n  attr_accessor :current_user, :role\n  def admin? = role == :admin\nend\n\nroutes = ActionDispatch::Routing::RouteSet.new\nroutes.draw { get \"/accounts/:id\", to: \"accounts#show\" }\nReusePocController.include routes.url_helpers\n\nclass AdminPanelComponent \u003c ViewComponent::Base\n  def render? = helpers.admin?\n\n  def call\n    href = helpers.url_for(controller: \"accounts\", action: \"show\", id: 42, only_path: false)\n    \"ADMIN user=#{helpers.current_user};host=#{request.host};href=#{href}\".html_safe\n  end\nend\n\nclass UrlOnlyComponent \u003c ViewComponent::Base\n  def call\n    href = helpers.url_for(controller: \"accounts\", action: \"show\", id: 42, only_path: false)\n    \"user=#{helpers.current_user};host=#{request.host};href=#{href}\".html_safe\n  end\nend\n\nclass SlotChildComponent \u003c ViewComponent::Base\n  def call = \"child_user=#{helpers.current_user};child_path=#{request.path}\".html_safe\nend\n\nclass SlotParentComponent \u003c ViewComponent::Base\n  renders_one :child, SlotChildComponent\n  def call = \"parent_user=#{helpers.current_user};parent_path=#{request.path};\".html_safe + child.to_s\nend\n\nclass RaceComponent \u003c ViewComponent::Base\n  def before_render = sleep 0.05\n  def call = \"#{helpers.current_user}@#{request.path}\".html_safe\nend\n\ndef vc(user:, role:, path:, host: \"app.example\")\n  c = ReusePocController.new\n  c.current_user = user\n  c.role = role\n  c.set_request!(ActionDispatch::Request.new(Rack::MockRequest.env_for(path, \"HTTP_HOST\" =\u003e host)))\n  c.set_response!(ActionDispatch::Response.new)\n  c.view_context\nend\n\nadmin_vc = vc(user: \"alice\", role: :admin, path: \"/admin\", host: \"admin.example\")\nguest_vc = vc(user: \"bob\", role: :guest, path: \"/guest\", host: \"app.example\")\n\npanel = AdminPanelComponent.new\nputs \"auth_admin_first=#{panel.render_in(admin_vc)}\"\nputs \"auth_guest_reused=#{panel.render_in(guest_vc)}\"\nputs \"auth_guest_fresh=#{AdminPanelComponent.new.render_in(guest_vc).inspect}\"\n\nurl = UrlOnlyComponent.new\nputs \"host_attacker_prime=#{url.render_in(vc(user: \"attacker\", role: :guest, path: \"/prime\", host: \"evil.example\"))}\"\nputs \"host_victim_reused=#{url.render_in(vc(user: \"victim\", role: :guest, path: \"/account\", host: \"app.example\"))}\"\nputs \"host_victim_fresh=#{UrlOnlyComponent.new.render_in(vc(user: \"victim\", role: :guest, path: \"/account\", host: \"app.example\"))}\"\n\nparent = SlotParentComponent.new\nputs \"slot_admin_first=#{parent.render_in(admin_vc) { |p| p.with_child }}\"\nputs \"slot_guest_reused=#{parent.render_in(guest_vc) { |p| p.with_child }}\"\nputs \"slot_guest_fresh=#{SlotParentComponent.new.render_in(guest_vc) { |p| p.with_child }}\"\n\nrace = RaceComponent.new\nq = Queue.new\nt1 = Thread.new { q \u003c\u003c [:admin, race.render_in(vc(user: \"admin\", role: :admin, path: \"/admin\"))] }\nt2 = Thread.new { q \u003c\u003c [:guest, race.render_in(vc(user: \"guest\", role: :guest, path: \"/guest\"))] }\nt1.join\nt2.join\nresults = 2.times.map { q.pop }.to_h\nputs \"race_admin_thread=#{results[:admin]}\"\nputs \"race_guest_thread=#{results[:guest]}\"\n```\n\nObserved output:\n\n```text\nauth_admin_first=ADMIN user=alice;host=admin.example;href=http://admin.example/accounts/42\nauth_guest_reused=ADMIN user=alice;host=admin.example;href=http://admin.example/accounts/42\nauth_guest_fresh=\"\"\n\nhost_attacker_prime=user=attacker;host=evil.example;href=http://evil.example/accounts/42\nhost_victim_reused=user=attacker;host=evil.example;href=http://evil.example/accounts/42\nhost_victim_fresh=user=victim;host=app.example;href=http://app.example/accounts/42\n\nslot_admin_first=parent_user=alice;parent_path=/admin;child_user=alice;child_path=/admin\nslot_guest_reused=parent_user=alice;parent_path=/admin;child_user=alice;child_path=/guest\nslot_guest_fresh=parent_user=bob;parent_path=/guest;child_user=bob;child_path=/guest\n\nrace_admin_thread=admin@/guest\nrace_guest_thread=admin@/guest\n```\n\n## Authorization-Impact PoC\n\nThe following PoC models a realistic downstream application pattern: a shared component registry caches component objects instead of caching component classes, factories, or rendered strings. An admin request primes the cached toolbar component. A later guest request renders the same cached object.\n\nThe component uses `render?` as an authorization-aware visibility gate and emits a representative privileged action link.\n\n```ruby\n$LOAD_PATH.unshift File.expand_path(\"lib\", Dir.pwd)\nrequire \"action_controller/railtie\"\nrequire \"rack/mock\"\nrequire \"view_component/base\"\n\nmodule SharedComponentRegistry\n  def self.admin_toolbar\n    @admin_toolbar ||= AdminToolbarComponent.new\n  end\n\n  def self.reset!\n    remove_instance_variable(:@admin_toolbar) if defined?(@admin_toolbar)\n  end\nend\n\nUser = Struct.new(:id, :role, keyword_init: true) do\n  def admin? = role == :admin\nend\n\nclass AppController \u003c ActionController::Base\n  helper_method :current_user, :admin?\n  attr_accessor :current_user\n\n  def admin?\n    current_user\u0026.admin?\n  end\nend\n\nroutes = ActionDispatch::Routing::RouteSet.new\nroutes.draw do\n  get \"/admin/users/:id/impersonate\", to: \"admin/users#impersonate\", as: :impersonate_admin_user\nend\nAppController.include routes.url_helpers\n\nclass AdminToolbarComponent \u003c ViewComponent::Base\n  def render?\n    helpers.admin?\n  end\n\n  def call\n    helpers.link_to(\n      \"Impersonate user 42\",\n      helpers.impersonate_admin_user_url(42, host: request.host),\n      data: { turbo_method: :post }\n    )\n  end\nend\n\nclass DashboardController \u003c AppController\n  def render_dashboard_with_shared_component\n    render_to_string(inline: \u0027\u003cmain\u003e\u003ch1\u003eDashboard\u003c/h1\u003e\u003c%= render SharedComponentRegistry.admin_toolbar %\u003e\u003c/main\u003e\u0027)\n  end\n\n  def render_dashboard_with_fresh_component\n    render_to_string(inline: \u0027\u003cmain\u003e\u003ch1\u003eDashboard\u003c/h1\u003e\u003c%= render AdminToolbarComponent.new %\u003e\u003c/main\u003e\u0027)\n  end\nend\n\ndef controller_for(user:, host:, path: \"/dashboard\")\n  c = DashboardController.new\n  c.current_user = user\n  c.set_request!(ActionDispatch::Request.new(Rack::MockRequest.env_for(path, \"HTTP_HOST\" =\u003e host)))\n  c.set_response!(ActionDispatch::Response.new)\n  c\nend\n\nSharedComponentRegistry.reset!\nadmin = User.new(id: 1, role: :admin)\nguest = User.new(id: 2, role: :guest)\n\nadmin_response = controller_for(user: admin, host: \"admin.example\").render_dashboard_with_shared_component\nguest_reused_response = controller_for(user: guest, host: \"app.example\").render_dashboard_with_shared_component\nguest_fresh_response = controller_for(user: guest, host: \"app.example\").render_dashboard_with_fresh_component\n\nputs \"admin_shared_contains_admin_link=#{admin_response.include?(\u0027/admin/users/42/impersonate\u0027)}\"\nputs \"guest_reused_contains_admin_link=#{guest_reused_response.include?(\u0027/admin/users/42/impersonate\u0027)}\"\nputs \"guest_fresh_contains_admin_link=#{guest_fresh_response.include?(\u0027/admin/users/42/impersonate\u0027)}\"\nputs \"guest_reused_contains_admin_host=#{guest_reused_response.include?(\u0027http://admin.example/admin/users/42/impersonate\u0027)}\"\nputs \"guest_reused_response=#{guest_reused_response.gsub(/\\s+/, \u0027 \u0027).strip}\"\nputs \"guest_fresh_response=#{guest_fresh_response.gsub(/\\s+/, \u0027 \u0027).strip.inspect}\"\n```\n\nObserved output:\n\n```text\nadmin_shared_contains_admin_link=true\nguest_reused_contains_admin_link=true\nguest_fresh_contains_admin_link=false\nguest_reused_contains_admin_host=true\nguest_reused_response=\u003cmain\u003e\u003ch1\u003eDashboard\u003c/h1\u003e\u003ca data-turbo-method=\"post\" href=\"http://admin.example/admin/users/42/impersonate\"\u003eImpersonate user 42\u003c/a\u003e\u003c/main\u003e\nguest_fresh_response=\"\u003cmain\u003e\u003ch1\u003eDashboard\u003c/h1\u003e\u003c/main\u003e\"\n```\n\nThis confirms a cross-user authorization impact in a realistic pattern: a guest receives privileged UI that a fresh component correctly suppresses. It also confirms stale request and Host context in the generated privileged URL.\n\n## Exploit Scenario\n\nA downstream app stores component instances in a constant, singleton service, memoized helper, cache object, or shared collection builder to avoid allocation. An attacker or lower-privileged user later triggers rendering of that same object.\n\nPotential real-world examples:\n\n- A navigation/sidebar component checks `helpers.admin?` in `render?`.\n- A tenant switcher uses `request.host` or `current_user.account`.\n- A component emits absolute URLs or signed action links.\n- A table uses slot child components that rely on helper/request state.\n- A global UI registry stores instantiated spacer or child components.\n\nIn these cases, a component first rendered under an admin or attacker-controlled request can affect later renders for other users.\n\n## Impact\n\nConfirmed impact classes:\n\n- stale privileged UI rendering\n- stale user identity through `helpers`\n- stale Host/request data in generated absolute URLs\n- slot child context inheritance\n- cross-thread context corruption\n- stale format/variant template selection\n- stale `view_flow` / `content_for` writes\n- collection and spacer component context leakage\n\nThis can chain into privilege escalation if an application relies on UI visibility as an authorization boundary. It can also leak signed links, tenant-specific URLs, admin actions, or user-specific data.\n\n## Preconditions\n\n- The same component, collection, slot, or spacer component instance is reused across render contexts.\n- The component reads request-scoped or user-scoped APIs such as `helpers`, `controller`, `request`, URL helpers, `render?`, `before_render`, slots, variants, formats, or `content_for`.\n- Higher impact when the shared object crosses users, tenants, roles, or threads.\n\nNormal per-request usage such as `render(MyComponent.new(...))` is not affected.\n\n## Chaining Potential\n\nThis issue can chain with:\n\n- UI-only authorization checks\n- signed admin links embedded in components\n- Host header poisoning\n- multi-tenant routing based on host/subdomain\n- shared component registries\n- fragment/component caching patterns that cache objects rather than rendered strings\n- concurrent Rails servers such as Puma\n\nThe framework alone does not directly prove account takeover, but downstream applications can reach high impact if stale component output exposes privileged action links or bypasses server-side authorization assumptions.\n\n## Remediation\n\nThe safest fix is to make component and collection instances one-shot renderables.\n\nRecommended options:\n\n1. Add a runtime guard in `render_in` that raises or warns when the same component instance is rendered again with a different `view_context`.\n2. Reset render-scoped ivars at the beginning of every render, including:\n   - `__vc_original_view_context`\n   - `@lookup_context`\n   - `@view_flow`\n   - `@__vc_requested_details`\n   - `@__vc_controller`\n   - `@__vc_helpers`\n   - `@__vc_request`\n3. Rebuild `ViewComponent::Collection` child component instances per render or document/enforce collections as one-shot.\n4. Avoid accepting a reusable instantiated `spacer_component`, or reset/clone it before rendering.\n5. Add thread-safety tests for concurrent rendering of a shared instance.",
  "id": "GHSA-9h85-g7w3-rh49",
  "modified": "2026-07-15T22:51:33Z",
  "published": "2026-07-15T22:51:33Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/ViewComponent/view_component/security/advisories/GHSA-9h85-g7w3-rh49"
    },
    {
      "type": "WEB",
      "url": "https://github.com/ViewComponent/view_component/commit/7b05073be28037f7d5ff141e9dd42f3cf47956a4"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/ViewComponent/view_component"
    },
    {
      "type": "WEB",
      "url": "https://github.com/ViewComponent/view_component/releases/tag/v4.12.0"
    },
    {
      "type": "WEB",
      "url": "https://github.com/rubysec/ruby-advisory-db/blob/master/gems/view_component/CVE-2026-54497.yml"
    },
    {
      "type": "WEB",
      "url": "https://www.cve.org/CVERecord/SearchResults?query=CVE-2026-54497"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "ViewComponent: Reused Component Instances Retain Stale Render Context"
}

No mitigation information available for this CWE.

No CAPEC attack patterns related to this CWE.