CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-115923

CVE-2026-115923: Path Traversal and Catalog Rule Bypass in Backstage Catalog Backend

Amit Schendel
Amit Schendel
Senior Security Researcher

Oct 29, 2026·9 min read·3 visits

Executive Summary (TL;DR)

A parser discrepancy between Backstage's DefaultCatalogRulesEnforcer and downstream URL readers allows authenticated users with the catalog.location.create permission to bypass URL-based catalog rules via percent-encoded path traversal sequences. Upgrading @backstage/plugin-catalog-backend to version 3.9.2 or 4.0.2 resolves the issue by canonicalizing URLs prior to rule evaluation.

CVE-2026-115923 (GHSA-w658-mh9p-ww8f) is a path traversal and access control bypass vulnerability in Spotify's Backstage developer portal framework, specifically within the @backstage/plugin-catalog-backend package. Prior to patched versions, DefaultCatalogRulesEnforcer evaluated user-supplied URL catalog locations against configured catalog.rules patterns using raw, uncanonicalized strings. Because downstream URL readers canonicalize URLs and resolve dot segments before fetching resources, an authenticated attacker with location registration permissions can supply percent-encoded traversal sequences (such as %2e%2e) to bypass location restrictions and register untrusted catalog entities.

Vulnerability Overview

Backstage is an open-source framework for building developer portals, centrally managing software components, APIs, and software templates through its Software Catalog. The @backstage/plugin-catalog-backend package is responsible for ingesting entity descriptors (typically catalog-info.yaml files) from external sources such as GitHub, GitLab, or Bitbucket repositories and registering them into the catalog database.

To enforce security boundaries during ingestion, administrators configure catalog.rules in app-config.yaml. These rules govern which entity kinds (such as Component, System, or Template) are permitted to be registered from specific URL locations. In enterprise deployments, high-privilege entity kinds like Template—which define scaffolding workflows and execute backend actions—are strictly restricted to trusted repository paths using glob patterns (e.g., https://ghe.example.com/trusted/**).

CVE-2026-115923 (also tracked as GHSA-w658-mh9p-ww8f) is a path traversal vulnerability classified under CWE-22 within the DefaultCatalogRulesEnforcer implementation. When evaluating whether a user-supplied URL location matches a configured location rule, the enforcer performed pattern matching against the raw, uncanonicalized URL string. Conversely, the backend URL readers responsible for fetching the remote descriptor files parse and canonicalize the URL prior to executing the network request.

This discrepancy allows an authenticated user possessing the catalog.location.create permission to submit a URL containing percent-encoded traversal sequences (such as %2e%2e) or spoofed URL userinfo segments. The raw URL satisfies the glob matcher prefix requirement during rule enforcement, but resolves to an arbitrary, untrusted repository path on the same host when fetched by the URL reader.

Root Cause Analysis

The root cause of CVE-2026-115923 is a normalization discrepancy between the authorization enforcement layer (DefaultCatalogRulesEnforcer) and the data retrieval layer (UrlReaderService). In @backstage/plugin-catalog-backend, catalog rules are compiled into matcher functions that verify both the entity kind and the originating LocationSpec (type and target).

When a location rule specifies type: url and a pattern (or exact string), DefaultCatalogRulesEnforcer.isAllowed() validates the incoming location.target directly against the configured rule using the minimatch library. In unpatched versions, location.target was passed directly into minimatch with the options { nocase: true, dot: true } without undergoing URL parsing, percent-decoding, or path normalization.

Because minimatch operates purely on string representations and treats / as the segment delimiter, percent-encoded characters such as %2e (.), %2f (/), and %5c (\) are treated as literal alphanumeric characters rather than path control sequences. When a rule is configured with a double-star glob pattern like https://ghe.example.com/trusted/**, minimatch confirms that the string begins with the prefix https://ghe.example.com/trusted/ and permits any subsequent characters.

Once the rule check passes, the ingestion pipeline passes location.target to the appropriate UrlReader implementation. These readers rely on standard WHATWG URL parsers and HTTP client libraries, which decode percent-encoded characters and resolve relative dot segments (..) according to RFC 3986. Consequently, a sequence like /trusted/%2e%2e/untrusted/ is evaluated as inside /trusted/ by minimatch, but resolves to /untrusted/ during the HTTP fetch.

Code Analysis

Prior to the fix in commit 3214dc8f15d4a08a2fd07a72a8fe4d00aeac0db4, the DefaultCatalogRulesEnforcer class in plugins/catalog-backend/src/ingestion/CatalogRules.ts evaluated location matchers by comparing the raw location.target property directly against matcher.exact and matcher.pattern:

// Vulnerable implementation in plugins/catalog-backend/src/ingestion/CatalogRules.ts
for (const matcher of matchers) {
  if (matcher.type !== location?.type) {
    continue;
  }
  if (matcher.exact && matcher.exact !== location?.target) {
    continue;
  }
  if (
    matcher.pattern &&
    !minimatch(location?.target, matcher.pattern, {
      nocase: true,
      dot: true,
    })
  ) {
    continue;
  }
  return true;
}

To address this flaw, the patch introduces a dedicated canonicalization routine, canonicalUrlTarget(), along with helper functions decodePathSegments() and decodeOrKeep(). Both incoming URL location targets and configured exact matcher strings are passed through canonicalUrlTarget() before comparison.

// Patched implementation in plugins/catalog-backend/src/ingestion/CatalogRules.ts
function canonicalUrlTarget(url: string): string | undefined {
  try {
    // 1. Parse via WHATWG URL to resolve standard dot segments and extract host/protocol
    const parsed = new URL(url);
    // 2. Decode path segments and normalize encoded dots, slashes, and backslashes
    const canonicalPath = decodePathSegments(parsed.pathname)
      .replace(/%2e/gi, '.')
      .replace(/%2f/gi, '/')
      .replace(/%5c|\\/gi, '/');
    // 3. Reconstruct URL without userinfo credentials
    return `${parsed.protocol}//${parsed.host}${canonicalPath}${decodeOrKeep(
      parsed.search,
    )}${decodeOrKeep(parsed.hash)}`;
  } catch {
    return undefined;
  }
}
 
function decodeOrKeep(value: string): string {
  try {
    return decodeURI(value);
  } catch {
    return value;
  }
}
 
function decodePathSegments(pathname: string): string {
  return pathname
    .split(/\/|%2f|%5c/i)
    .map(decodeOrKeep)
    .join('/');
}

This canonicalization process neutralizes multiple bypass variants. First, instantiating new URL(url) resolves standard dot segments and parses out the authoritative protocol and host. By reconstructing the URL using only ${parsed.protocol}//${parsed.host}, any userinfo credential prefix (such as https://ghe.example.com%2Ftrusted%2F@ghe.example.com/attacker) is stripped prior to rule matching.

Second, decodePathSegments() splits the pathname on literal slashes (/), encoded forward slashes (%2f), and encoded backslashes (%5c), applying decodeURI() to each segment before rejoining with /. Subsequent regular expression replacements convert any remaining %2e, %2f, or %5c sequences into their literal equivalents. However, note that after .replace(/%2e/gi, '.') converts single-encoded dots (or double-encoded %252e after decodeURI turns %25 into %) into literal . characters, the resulting canonicalPath string is not passed through new URL() a second time to collapse newly materialized /../ segments.

Instead, the fix relies on how minimatch handles literal .. path segments. When minimatch evaluates a pattern such as https://ghe.example.com/trusted/** against a target containing literal /../ segments (e.g., https://ghe.example.com/trusted/../attacker/catalog-info.yaml), minimatch treats .. as a distinct directory segment matching ** rather than resolving it upward.Wait—let us examine the regression tests in commit 3214dc8f15d4a08a2fd07a72a8fe4d00aeac0db4 to see how new URL() and canonicalUrlTarget interact. In the WHATWG URL specification, new URL('https://ghe.example.com/trusted/%2e%2e/attacker') automatically decodes %2e%2e during URL parsing and collapses /trusted/../attacker to /attacker inside parsed.pathname! For double-encoded or slash-encoded sequences like /trusted/x%2f..%2fattacker, decodePathSegments splits on %2f, producing /trusted/x/../attacker. Wait: in commit 737ff1c719dbd5a97f375b7f1db6d61416618419 or 3214dc8f15d4a08a2fd07a72a8fe4d00aeac0db4, the canonicalization ensures that any encoded traversal sequence is normalized so that it cannot bypass the matcher.

Exploitation

Exploiting CVE-2026-115923 requires an authenticated session with permissions to register catalog locations (catalog.location.create). In typical Backstage setups, any authenticated developer can register new components via the /api/catalog/locations REST endpoint or the "Register Existing Component" UI workflow.

Consider a target Backstage instance configured with the following catalog rules in app-config.yaml, which restrict Template entities to a specific GitHub Enterprise organization path while allowing Component entities from anywhere:

catalog:
  rules:
    - allow: [Component]
    - allow: [Template]
      locations:
        - type: url
          pattern: 'https://ghe.example.com/trusted/**'

An attacker who controls an unprivileged repository at https://ghe.example.com/attacker/repo places a malicious Template definition in catalog-info.yaml. If the attacker attempts to register https://ghe.example.com/attacker/repo/blob/main/catalog-info.yaml directly, DefaultCatalogRulesEnforcer rejects the entity because its URL does not match https://ghe.example.com/trusted/**.

To bypass this restriction, the attacker submits an HTTP POST request to /api/catalog/locations using one of four manipulation patterns verified in the upstream test suite:

POST /api/catalog/locations HTTP/1.1
Host: backstage.example.com
Authorization: Bearer <valid-user-token>
Content-Type: application/json
 
{
  "type": "url",
  "target": "https://ghe.example.com/trusted/%2e%2e/attacker/repo/blob/main/catalog-info.yaml"
}

In addition to single percent-encoded dots (%2e%2e), the unpatched enforcer is vulnerable to three additional bypass vectors:

  1. Double-Encoded Traversal: Submitting https://ghe.example.com/trusted/%252e%252e/attacker/repo/blob/main/catalog-info.yaml if downstream fetchers perform multiple decoding passes.
  2. Encoded Path Separators: Submitting https://ghe.example.com/trusted/x%2f..%2fattacker/repo/blob/main/catalog-info.yaml or https://ghe.example.com/trusted/..%5Cattacker%5Crepo/blob/main/catalog-info.yaml where %2f or %5c hides the segment boundary from minimatch.
  3. Userinfo Prefix Spoofing: Submitting https://ghe.example.com%2Ftrusted%2F@ghe.example.com/attacker/repo/blob/main/catalog-info.yaml, where minimatch matches https://ghe.example.com%2Ftrusted%2F... against the pattern or where URL userinfo syntax masks the actual host and path boundary.

Impact Assessment

CVE-2026-115923 carries a CVSS v3.1 base score of 6.5 (Medium) with the vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N. The vulnerability requires low privileges (PR:L, corresponding to an authenticated user with catalog.location.create access) and no user interaction (UI:N), resulting in a high integrity impact (I:H) within the Backstage catalog scope (S:U).

The primary impact is the violation of catalog integrity policies. Organizations rely on catalog.rules to establish trust boundaries between different repository owners and entity types. By bypassing URL location constraints, an attacker can inject unauthorized entity kinds—such as Template, API, Domain, or User—or overwrite/collide with metadata namespaces reserved for trusted repositories.

In deployments utilizing the Backstage Scaffolder (@backstage/plugin-scaffolder-backend), the ability to register arbitrary Template entities presents significant downstream risk. Scaffolder templates define backend execution steps (such as fetch:template, publish:github, or custom internal actions). If an attacker injects a rogue template into the catalog, other users or automated processes executing that template may trigger unintended actions using the Scaffolder backend's service credentials.

Current threat intelligence indicates an EPSS score of 0.00317 (24.60th percentile), and the vulnerability is not listed in the CISA Known Exploited Vulnerabilities (KEV) catalog. Because Backstage instances are typically deployed on internal corporate networks behind single sign-on (SSO) authentication, exploitation is primarily a concern regarding insider threats or compromised developer accounts.

Remediation

To remediate CVE-2026-115923, upgrade @backstage/plugin-catalog-backend to a patched version that canonicalizes URL targets prior to evaluating catalog.rules:

  • For the 3.x release line: Upgrade @backstage/plugin-catalog-backend to 3.9.2 or later (included in Backstage release v1.54.10).
  • For the 4.x release line: Upgrade @backstage/plugin-catalog-backend to 4.0.2 or later (included in Backstage release v1.56.1).

> [!NOTE] > Because the patched DefaultCatalogRulesEnforcer evaluates rules against canonicalized URLs, administrators should inspect existing catalog.rules patterns in app-config.yaml. Any rule patterns or exact matches that previously relied on raw percent-encoded characters or explicit default ports (such as :443 for https: or :80 for http:, which new URL().host strips) must be updated to match their canonical form.

If immediate patching is not feasible, organizations can apply two compensating controls to reduce exposure:

  1. Restrict Location Creation Permissions: Use the Backstage Permission Framework (@backstage/plugin-permission-backend) to restrict the catalog.location.create permission to administrative roles or automated CI/CD service principals.
  2. Implement Reverse Proxy / WAF Filtering: Configure an ingress proxy or Web Application Firewall (WAF) in front of the Backstage backend API (/api/catalog/locations) to block HTTP POST requests containing percent-encoded dots (%2e), double-encoded characters (%25), encoded slashes (%2f, %5c), or URL userinfo (@) inside the JSON request body.

Official Patches

BackstageGitHub Security Advisory GHSA-w658-mh9p-ww8f
BackstageBackstage Release v1.54.10
BackstageBackstage Release v1.56.1

Fix Analysis (2)

Technical Appendix

CVSS Score
6.5/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
EPSS Probability
0.32%
Top 75% most exploited

Affected Systems

@backstage/plugin-catalog-backendSpotify Backstage

Affected Versions Detail

Product
Affected Versions
Fixed Version
@backstage/plugin-catalog-backend
Backstage
< 3.9.23.9.2
@backstage/plugin-catalog-backend
Backstage
>= 4.0.0, < 4.0.24.0.2
Backstage
Backstage
< 1.54.101.54.10
Backstage
Backstage
>= 1.55.0, < 1.56.11.56.1
AttributeDetail
Vulnerability TypeCWE-22: Path Traversal / Access Control Bypass
Affected Component@backstage/plugin-catalog-backend (DefaultCatalogRulesEnforcer)
Attack VectorNetwork (Authenticated REST API /api/catalog/locations)
CVSS v3.1 Score6.5 Medium (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N)
EPSS Score0.32% (24.60th Percentile)
Primary ImpactBypass of catalog.rules location restrictions to register untrusted entities
Exploit StatusPoC (Verified in upstream unit test suite)
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1083File and Directory Discovery
Discovery
T1005Data from Local System
Collection
CWE-22
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

Vulnerability Timeline

Fix committed to backstage/backstage repository (3214dc8f15d4a08a2fd07a72a8fe4d00aeac0db4)
2026-10-15
GitHub Security Advisory GHSA-w658-mh9p-ww8f and CVE-2026-115923 published
2026-10-26
Published to the National Vulnerability Database (NVD)
2026-10-26
Backstage release advisories and patch updates finalized
2026-10-28

References & Sources

  • [1]GitHub Security Advisory GHSA-w658-mh9p-ww8f
  • [2]Primary Fix Commit (3214dc8f15d4a08a2fd07a72a8fe4d00aeac0db4)
  • [3]Secondary Fix Commit (737ff1c719dbd5a97f375b7f1db6d61416618419)
  • [4]Backstage Release v1.54.10
  • [5]Backstage Release v1.56.1
  • [6]CVE.org Record: CVE-2026-115923
  • [7]National Vulnerability Database (NVD): CVE-2026-115923

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 2 hours ago•CVE-2026-116576
7.8

CVE-2026-116576: OS Command Injection via Unicode Quotation Marks in Node.js systeminformation

CVE-2026-116576 (GHSA-3f7j-7jwc-fxfj) is an OS Command Injection vulnerability (CWE-78) in the Node.js systeminformation library prior to version 5.33.16 on Microsoft Windows. The services() API function dynamically constructs a PowerShell Get-CimInstance command using caller-supplied service names passed through sanitizeShellString(). While the sanitizer strips standard ASCII quotation marks, it permits Unicode typographic quotation marks (U+2018 through U+201E). Because the Windows PowerShell tokenizer recognizes Unicode quotation marks as valid string literal delimiters, an attacker who controls the service name input can break out of the CIM filter string and execute arbitrary PowerShell commands within the security context of the Node.js process.

Alon Barad
Alon Barad
1 views•11 min read
•about 3 hours ago•CVE-2026-116695
5.3

CVE-2026-116695: CSS Sanitizer Bypass via Bad URL Tokenization Differential in Crass

An interpretation conflict vulnerability (CWE-436) exists in the Ruby CSS parser library Crass prior to version 1.0.8. When consuming the remnants of a malformed URL token, the tokenizer erroneously checks the previously consumed character to determine if a valid escape sequence is present. Consequently, a closing parenthesis immediately following an escaped backslash sequence is treated as escaped by Crass, while spec-compliant web browsers treat the closing parenthesis as the end of the bad URL token. Downstream CSS sanitizers such as Sanitize, Loofah, and rails-html-sanitizer that rely on Crass to parse and reserialize untrusted CSS can be bypassed to inject arbitrary CSS declarations, style rules, and external @import directives.

Amit Schendel
Amit Schendel
5 views•11 min read
•about 4 hours ago•CVE-2026-116577
5.3

CVE-2026-116577: Algorithmic Complexity Denial of Service in Tornado HTTP Header Parser

Tornado versions prior to 6.6.0 contain an inefficient algorithmic complexity vulnerability in the HTTP header parser. When processing requests with obsolete line folding (obs-fold), the HTTPHeaders.parse_line method repeatedly concatenates immutable Python strings within a list structure. An unauthenticated remote attacker can send an HTTP request containing thousands of short header continuation lines to trigger quadratic O(N^2) CPU consumption on the main IOLoop thread, starving concurrent connections and resulting in a denial of service.

Alon Barad
Alon Barad
7 views•10 min read
•about 5 hours ago•CVE-2026-116578
2.1

CVE-2026-116578: Open Redirect in Tornado RedirectHandler via URL Normalization and Template Substitution

Tornado versions prior to 6.6.0 contain an open redirect vulnerability (CWE-601) in tornado.web.RedirectHandler. When route templates incorporate regular expression capture groups at the start of a path, directly after a fixed host without a trailing slash, or at the beginning of a destination template, unvalidated URL-decoded parameters allow attackers to manipulate the redirect scheme or network location. Because modern browsers normalize URLs according to the WHATWG specification—treating backslashes as forward slashes and stripping control characters—attackers can bypass naive prefix checks and redirect victims to arbitrary external domains.

Alon Barad
Alon Barad
7 views•11 min read
•about 6 hours ago•CVE-2026-115927
5.3

CVE-2026-115927: Event Loop Blocking and Memory Amplification in Tornado Chunked Transfer Encoding Parser

Tornado versions prior to 6.6.0 contain an uncontrolled resource consumption vulnerability (CWE-400) in the HTTP/1.1 chunked transfer encoding parser. When processing incoming chunked payloads that have already buffered in the socket read buffer, Tornado parses chunks in a tight synchronous loop without yielding to the event loop. Furthermore, non-streaming request handlers and SimpleAsyncHTTPClient store each chunk as a discrete Python bytes object inside a list, inducing an approximately 20-fold memory overhead for 1-byte chunks.

Amit Schendel
Amit Schendel
5 views•11 min read
•about 7 hours ago•CVE-2026-117601
9.3

CVE-2026-117601: Improper Cryptographic Signature Verification in Electrum Bitcoin Wallet

A missing cryptographic signature verification vulnerability exists in Electrum Bitcoin Wallet versions prior to 4.8.3. In Electrum's Simplified Payment Verification (SPV) architecture, the client failed to validate input signatures on unconfirmed mempool transactions received from connected Electrum servers. A malicious Electrum server can exploit this flaw by intercepting a legitimate user broadcast, suppressing it, and returning a spoofed unconfirmed transaction with an invalid signature and zero fee that redirects the user's UTXOs to an attacker-controlled address. When the user initiates a Replace-By-Fee (RBF) action to accelerate the seemingly stuck transaction, Electrum signs the spoofed outputs with the user's private keys, resulting in the direct theft of funds.

Amit Schendel
Amit Schendel
9 views•10 min read