VYPR
Moderate severityOSV Advisory· Published Jan 30, 2026· Updated Feb 2, 2026

@backstage/plugin-techdocs-node vulnerable to possible Path Traversal in TechDocs Local Generator

CVE-2026-25152

Description

Backstage is an open framework for building developer portals, and @backstage/plugin-techdocs-node provides common node.js functionalities for TechDocs. In versions of @backstage/plugin-techdocs-node prior to 1.13.11 and 1.14.1, a path traversal vulnerability in the TechDocs local generator allows attackers to read arbitrary files from the host filesystem when Backstage is configured with techdocs.generator.runIn: local. When processing documentation from untrusted sources, symlinks within the docs directory are followed by MkDocs during the build process. File contents are embedded into generated HTML and exposed to users who can view the documentation. This vulnerability is fixed in @backstage/plugin-techdocs-node versions 1.13.11 and 1.14.1. Some workarounds are available. Switch to runIn: docker in app-config.yaml and/or restrict write access to TechDocs source repositories to trusted users only.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
@backstage/plugin-techdocs-nodenpm
>= 1.14.0, < 1.14.11.14.1
@backstage/plugin-techdocs-nodenpm
< 1.13.111.13.11

Affected products

1

Patches

1
08f388e3394b

Merge pull request #31055 from secustor/feat/add-devtools-extension

https://github.com/backstage/backstageFredrik AdelöwJan 20, 2026via ghsa
28 files changed · +641 34
  • .changeset/slimy-ghosts-rescue.md+9 0 added
    @@ -0,0 +1,9 @@
    +---
    +'@backstage/plugin-catalog-unprocessed-entities': patch
    +'@backstage/plugin-devtools-react': minor
    +'@backstage/plugin-devtools': patch
    +'@backstage/plugin-devtools-backend': patch
    +'@backstage/plugin-devtools-common': patch
    +---
    +
    +Add support for adding `unprocessed-entities` and other tabs to `devtools` when using the New Frontend system
    
  • packages/app-next/app-config.yaml+5 0 modified
    @@ -114,6 +114,11 @@ app:
         # - entity-content:azure-devops/pull-requests
         # - entity-content:azure-devops/git-tags
     
    +    # Enable the catalog-unprocessed-entities tab in devtools
    +    - devtools-content:catalog-unprocessed-entities: true
    +    # Disable the catalog-unprocessed-entities element outside devtools
    +    - page:catalog-unprocessed-entities: false
    +
       # scmAuthExtension: >-
       #   createScmAuthExtension({
       #     id: 'apis.scmAuth.addons.ghe',
    
  • packages/app-next/package.json+1 0 modified
    @@ -57,6 +57,7 @@
         "@backstage/plugin-catalog-import": "workspace:^",
         "@backstage/plugin-catalog-react": "workspace:^",
         "@backstage/plugin-catalog-unprocessed-entities": "workspace:^",
    +    "@backstage/plugin-devtools": "workspace:^",
         "@backstage/plugin-home": "workspace:^",
         "@backstage/plugin-kubernetes": "workspace:^",
         "@backstage/plugin-kubernetes-cluster": "workspace:^",
    
  • packages/app-next/src/App.tsx+9 0 modified
    @@ -45,6 +45,8 @@ import { convertLegacyPageExtension } from '@backstage/core-compat-api';
     import { convertLegacyEntityContentExtension } from '@backstage/plugin-catalog-react/alpha';
     import { pluginInfoResolver } from './pluginInfoResolver';
     import { appModuleNav } from './modules/appModuleNav';
    +import devtoolsPlugin from '@backstage/plugin-devtools/alpha';
    +import { unprocessedEntitiesDevToolsContent } from '@backstage/plugin-catalog-unprocessed-entities/alpha';
     
     /*
     
    @@ -116,6 +118,11 @@ const notFoundErrorPageModule = createFrontendModule({
       extensions: [notFoundErrorPage],
     });
     
    +const devtoolsPluginUnprocessed = createFrontendModule({
    +  pluginId: 'catalog-unprocessed-entities',
    +  extensions: [unprocessedEntitiesDevToolsContent],
    +});
    +
     const collectedLegacyPlugins = convertLegacyAppRoot(
       <FlatRoutes>
         <Route path="/catalog-import" element={<CatalogImportPage />} />
    @@ -133,6 +140,8 @@ const app = createApp({
         notFoundErrorPageModule,
         appModuleNav,
         customHomePageModule,
    +    devtoolsPlugin,
    +    devtoolsPluginUnprocessed,
         ...collectedLegacyPlugins,
       ],
       advanced: {
    
  • plugins/catalog-unprocessed-entities/package.json+2 0 modified
    @@ -50,11 +50,13 @@
         "test": "backstage-cli package test"
       },
       "dependencies": {
    +    "@backstage/core-compat-api": "workspace:^",
         "@backstage/core-components": "workspace:^",
         "@backstage/core-plugin-api": "workspace:^",
         "@backstage/errors": "workspace:^",
         "@backstage/frontend-plugin-api": "workspace:^",
         "@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^",
    +    "@backstage/plugin-devtools-react": "workspace:^",
         "@material-ui/core": "^4.9.13",
         "@material-ui/icons": "^4.9.1",
         "@material-ui/lab": "^4.0.0-alpha.60",
    
  • plugins/catalog-unprocessed-entities/README.md+19 1 modified
    @@ -52,18 +52,36 @@ Import `catalogUnprocessedEntitiesPlugin` in your `App.tsx` and add it to your a
     
     ```typescript
     import catalogUnprocessedEntitiesPlugin from '@backstage/plugin-catalog-unprocessed-entities';
    +import { unprocessedEntitiesDevToolsContent } from '@backstage/plugin-catalog-unprocessed-entities/alpha';
     
    -// ...
    +// Optionally add unprocessed entities route to devtools
    +const devtoolsPluginUnprocessed = createFrontendModule({
    +  pluginId: 'catalog-unprocessed-entities',
    +  extensions: [unprocessedEntitiesDevToolsContent],
    +});
     
     export const app = createApp({
       features: [
         // ...
         catalogUnprocessedEntitiesPlugin,
    +
    +    // Optionally add unprocessed entities route to devtools
    +    devtoolsPluginUnprocessed,
    +    devtoolsPlugin, // devtools plugin needs to be added too, if autodiscover is disabled
         // ...
       ],
     });
     ```
     
    +```yaml
    +app:
    +  extensions:
    +    # Enable the catalog-unprocessed-entities tab in devtools
    +    - devtools-content:catalog-unprocessed-entities: true
    +    # Disable the catalog-unprocessed-entities element outside devtools including the sidebar
    +    - page:catalog-unprocessed-entities: false
    +```
    +
     ## Customization
     
     If you want to use the provided endpoints in a different way, you can use the ApiRef doing the following:
    
  • plugins/catalog-unprocessed-entities/report-alpha.api.md+28 0 modified
    @@ -6,6 +6,7 @@
     import { AnyApiFactory } from '@backstage/frontend-plugin-api';
     import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
     import { ApiFactory } from '@backstage/frontend-plugin-api';
    +import { DevToolsContentBlueprintParams } from '@backstage/plugin-devtools-react';
     import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
     import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
     import { IconComponent } from '@backstage/frontend-plugin-api';
    @@ -89,5 +90,32 @@ const _default: OverridableFrontendPlugin<
     >;
     export default _default;
     
    +// @alpha
    +export const unprocessedEntitiesDevToolsContent: OverridableExtensionDefinition<{
    +  kind: 'devtools-content';
    +  name: undefined;
    +  config: {
    +    path: string | undefined;
    +    title: string | undefined;
    +  };
    +  configInput: {
    +    title?: string | undefined;
    +    path?: string | undefined;
    +  };
    +  output:
    +    | ExtensionDataRef<string, 'core.routing.path', {}>
    +    | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
    +    | ExtensionDataRef<
    +        RouteRef_2<AnyRouteRefParams>,
    +        'core.routing.ref',
    +        {
    +          optional: true;
    +        }
    +      >
    +    | ExtensionDataRef<string, 'core.title', {}>;
    +  inputs: {};
    +  params: DevToolsContentBlueprintParams;
    +}>;
    +
     // (No @packageDocumentation comment for this package)
     ```
    
  • plugins/catalog-unprocessed-entities/src/alpha/devToolsContent.tsx+36 0 added
    @@ -0,0 +1,36 @@
    +/*
    + * Copyright 2024 The Backstage Authors
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +import { DevToolsContentBlueprint } from '@backstage/plugin-devtools-react';
    +
    +/**
    + * DevTools content for catalog unprocessed entities.
    + *
    + * @alpha
    + */
    +export const unprocessedEntitiesDevToolsContent = DevToolsContentBlueprint.make(
    +  {
    +    disabled: true,
    +    params: {
    +      path: 'unprocessed-entities',
    +      title: 'Unprocessed Entities',
    +      loader: () =>
    +        import('../components/UnprocessedEntities').then(m => (
    +          <m.UnprocessedEntitiesContent />
    +        )),
    +    },
    +  },
    +);
    
  • plugins/catalog-unprocessed-entities/src/alpha/index.ts+1 0 modified
    @@ -15,3 +15,4 @@
      */
     
     export { default } from './plugin';
    +export { unprocessedEntitiesDevToolsContent } from './devToolsContent';
    
  • plugins/devtools-backend/package.json+2 1 modified
    @@ -7,7 +7,8 @@
         "pluginPackages": [
           "@backstage/plugin-devtools",
           "@backstage/plugin-devtools-backend",
    -      "@backstage/plugin-devtools-common"
    +      "@backstage/plugin-devtools-common",
    +      "@backstage/plugin-devtools-react"
         ]
       },
       "publishConfig": {
    
  • plugins/devtools-common/package.json+2 1 modified
    @@ -8,7 +8,8 @@
         "pluginPackages": [
           "@backstage/plugin-devtools",
           "@backstage/plugin-devtools-backend",
    -      "@backstage/plugin-devtools-common"
    +      "@backstage/plugin-devtools-common",
    +      "@backstage/plugin-devtools-react"
         ]
       },
       "publishConfig": {
    
  • plugins/devtools/package.json+3 1 modified
    @@ -7,7 +7,8 @@
         "pluginPackages": [
           "@backstage/plugin-devtools",
           "@backstage/plugin-devtools-backend",
    -      "@backstage/plugin-devtools-common"
    +      "@backstage/plugin-devtools-common",
    +      "@backstage/plugin-devtools-react"
         ]
       },
       "publishConfig": {
    @@ -58,6 +59,7 @@
         "@backstage/errors": "workspace:^",
         "@backstage/frontend-plugin-api": "workspace:^",
         "@backstage/plugin-devtools-common": "workspace:^",
    +    "@backstage/plugin-devtools-react": "workspace:^",
         "@backstage/plugin-permission-react": "workspace:^",
         "@material-ui/core": "^4.9.13",
         "@material-ui/icons": "^4.9.1",
    
  • plugins/devtools-react/catalog-info.yaml+10 0 added
    @@ -0,0 +1,10 @@
    +apiVersion: backstage.io/v1alpha1
    +kind: Component
    +metadata:
    +  name: backstage-plugin-devtools-react
    +  title: '@backstage/plugin-devtools-react'
    +  description: Web library for the devtools plugin
    +spec:
    +  lifecycle: experimental
    +  type: backstage-web-library
    +  owner: maintainers
    
  • plugins/devtools-react/.eslintrc.js+1 0 added
    @@ -0,0 +1 @@
    +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
    
  • plugins/devtools-react/package.json+67 0 added
    @@ -0,0 +1,67 @@
    +{
    +  "name": "@backstage/plugin-devtools-react",
    +  "version": "0.0.0",
    +  "description": "Web library for the devtools plugin",
    +  "backstage": {
    +    "role": "web-library",
    +    "pluginId": "devtools",
    +    "pluginPackages": [
    +      "@backstage/plugin-devtools",
    +      "@backstage/plugin-devtools-backend",
    +      "@backstage/plugin-devtools-common",
    +      "@backstage/plugin-devtools-react"
    +    ]
    +  },
    +  "publishConfig": {
    +    "access": "public",
    +    "main": "dist/index.esm.js",
    +    "types": "dist/index.d.ts"
    +  },
    +  "repository": {
    +    "type": "git",
    +    "url": "https://github.com/backstage/backstage",
    +    "directory": "plugins/devtools-react"
    +  },
    +  "license": "Apache-2.0",
    +  "sideEffects": false,
    +  "main": "src/index.ts",
    +  "types": "src/index.ts",
    +  "files": [
    +    "dist"
    +  ],
    +  "scripts": {
    +    "build": "backstage-cli package build",
    +    "clean": "backstage-cli package clean",
    +    "lint": "backstage-cli package lint",
    +    "prepack": "backstage-cli package prepack",
    +    "postpack": "backstage-cli package postpack",
    +    "start": "backstage-cli package start",
    +    "test": "backstage-cli package test"
    +  },
    +  "dependencies": {
    +    "@backstage/core-plugin-api": "workspace:^",
    +    "@backstage/frontend-plugin-api": "workspace:^",
    +    "@material-ui/core": "^4.9.13"
    +  },
    +  "devDependencies": {
    +    "@backstage/cli": "workspace:^",
    +    "@backstage/test-utils": "workspace:^",
    +    "@testing-library/jest-dom": "^6.0.0",
    +    "@testing-library/react": "^14.0.0",
    +    "@types/react": "^18.0.0",
    +    "react": "^18.0.2",
    +    "react-dom": "^18.0.2",
    +    "react-router-dom": "^6.3.0"
    +  },
    +  "peerDependencies": {
    +    "@types/react": "^17.0.0 || ^18.0.0",
    +    "react": "^17.0.0 || ^18.0.0",
    +    "react-dom": "^17.0.0 || ^18.0.0",
    +    "react-router-dom": "^6.3.0"
    +  },
    +  "peerDependenciesMeta": {
    +    "@types/react": {
    +      "optional": true
    +    }
    +  }
    +}
    
  • plugins/devtools-react/README.md+5 0 added
    @@ -0,0 +1,5 @@
    +# @backstage/plugin-devtools-react
    +
    +Welcome to the web library package for the `devtools` plugin!
    +
    +_This plugin was created through the Backstage CLI_
    
  • plugins/devtools-react/report.api.md+50 0 added
    @@ -0,0 +1,50 @@
    +## API Report File for "@backstage/plugin-devtools-react"
    +
    +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
    +
    +```ts
    +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
    +import { ExtensionBlueprint } from '@backstage/frontend-plugin-api';
    +import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
    +import { JSX as JSX_2 } from 'react';
    +import { RouteRef } from '@backstage/frontend-plugin-api';
    +
    +// @public
    +export const DevToolsContentBlueprint: ExtensionBlueprint<{
    +  kind: 'devtools-content';
    +  params: DevToolsContentBlueprintParams;
    +  output:
    +    | ExtensionDataRef<string, 'core.routing.path', {}>
    +    | ExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
    +    | ExtensionDataRef<
    +        RouteRef<AnyRouteRefParams>,
    +        'core.routing.ref',
    +        {
    +          optional: true;
    +        }
    +      >
    +    | ExtensionDataRef<string, 'core.title', {}>;
    +  inputs: {};
    +  config: {
    +    path: string | undefined;
    +    title: string | undefined;
    +  };
    +  configInput: {
    +    title?: string | undefined;
    +    path?: string | undefined;
    +  };
    +  dataRefs: never;
    +}>;
    +
    +// @public
    +export interface DevToolsContentBlueprintParams {
    +  // (undocumented)
    +  loader: () => Promise<JSX_2.Element>;
    +  // (undocumented)
    +  path: string;
    +  // (undocumented)
    +  routeRef?: RouteRef;
    +  // (undocumented)
    +  title: string;
    +}
    +```
    
  • plugins/devtools-react/src/devToolsContentBlueprint.tsx+87 0 added
    @@ -0,0 +1,87 @@
    +/*
    + * Copyright 2024 The Backstage Authors
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +import {
    +  coreExtensionData,
    +  createExtensionBlueprint,
    +  ExtensionBoundary,
    +  RouteRef,
    +} from '@backstage/frontend-plugin-api';
    +import { JSX } from 'react';
    +
    +/**
    + * Parameters for creating a DevTools route extension
    + * @public
    + */
    +export interface DevToolsContentBlueprintParams {
    +  path: string;
    +  title: string;
    +  loader: () => Promise<JSX.Element>;
    +  routeRef?: RouteRef;
    +}
    +
    +/**
    + * Extension blueprint for creating DevTools content pages (appearing as tabs)
    + *
    + * @example
    + * ```tsx
    + * const myDevToolsContent = DevToolsContentBlueprint.make({
    + *  {
    + *     params: {
    + *       path: 'my-dev-tools',
    + *       title: 'My DevTools',
    + *       loader: () =>
    + *         import('../components/MyDevTools').then(m =>
    + *           compatWrapper(<m.MyDevTools />),
    + *         ),
    + *     },
    + *   },
    + * });
    + * ```
    + * @public
    + */
    +export const DevToolsContentBlueprint = createExtensionBlueprint({
    +  kind: 'devtools-content',
    +  attachTo: { id: 'page:devtools', input: 'contents' },
    +  output: [
    +    coreExtensionData.reactElement,
    +    coreExtensionData.routePath,
    +    coreExtensionData.routeRef.optional(),
    +    coreExtensionData.title,
    +  ],
    +  config: {
    +    schema: {
    +      path: z => z.string().optional(),
    +      title: z => z.string().optional(),
    +    },
    +  },
    +  *factory(params: DevToolsContentBlueprintParams, { node, config }) {
    +    const path = config.path ?? params.path;
    +    const title = config.title ?? params.title;
    +
    +    yield coreExtensionData.reactElement(
    +      ExtensionBoundary.lazy(node, params.loader),
    +    );
    +
    +    yield coreExtensionData.routePath(path);
    +
    +    yield coreExtensionData.title(title);
    +
    +    if (params.routeRef) {
    +      yield coreExtensionData.routeRef(params.routeRef);
    +    }
    +  },
    +});
    
  • plugins/devtools-react/src/index.ts+25 0 added
    @@ -0,0 +1,25 @@
    +/*
    + * Copyright 2025 The Backstage Authors
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +/**
    + * Web library for the devtools plugin.
    + *
    + * @packageDocumentation
    + */
    +export {
    +  type DevToolsContentBlueprintParams,
    +  DevToolsContentBlueprint,
    +} from './devToolsContentBlueprint';
    
  • plugins/devtools/README.md+33 0 modified
    @@ -166,6 +166,39 @@ You can also add tabs to show content from other plugins that fit well with the
     
     #### Catalog Unprocessed Entities Tab
     
    +##### New Frontend System
    +
    +Create an extension and/or load a 3rd party extension to add additional tabs.
    +
    +```shell
    +yarn --cwd plugins/<your-plugin> add @backstage/plugin-devtools-react
    +```
    +
    +```tsx
    +import { DevToolsContentBlueprint } from '@backstage/plugin-devtools-react';
    +
    +export const unprocessedEntitiesDevToolsContent = DevToolsContentBlueprint.make(
    +  {
    +    disabled: true,
    +    params: {
    +      path: 'unprocessed-entities',
    +      title: 'Unprocessed Entities',
    +      loader: () =>
    +        import('../components/UnprocessedEntities').then(m => (
    +          <m.UnprocessedEntitiesContent />
    +        )),
    +    },
    +  },
    +);
    +
    +const appFeature = createFrontendModule({
    +  pluginId: 'catalog-unprocessed-entities',
    +  extensions: [unprocessedEntitiesDevToolsContent],
    +});
    +```
    +
    +##### Old System
    +
     Here's how to add the Catalog Unprocessed Entities tab:
     
     1. Install and setup the [Catalog Unprocessed Entities plugin](https://github.com/backstage/backstage/tree/master/plugins/catalog-unprocessed-entities) as per its documentation
    
  • plugins/devtools/report-alpha.api.md+22 3 modified
    @@ -6,8 +6,10 @@
     import { AnyApiFactory } from '@backstage/frontend-plugin-api';
     import { AnyRouteRefParams } from '@backstage/frontend-plugin-api';
     import { ApiFactory } from '@backstage/frontend-plugin-api';
    +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
     import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
     import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
    +import { ExtensionInput } from '@backstage/frontend-plugin-api';
     import { IconComponent } from '@backstage/frontend-plugin-api';
     import { JSX as JSX_2 } from 'react';
     import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
    @@ -59,8 +61,6 @@ const _default: OverridableFrontendPlugin<
           };
         }>;
         'page:devtools': OverridableExtensionDefinition<{
    -      kind: 'page';
    -      name: undefined;
           config: {
             path: string | undefined;
           };
    @@ -77,7 +77,26 @@ const _default: OverridableFrontendPlugin<
                   optional: true;
                 }
               >;
    -      inputs: {};
    +      inputs: {
    +        contents: ExtensionInput<
    +          | ConfigurableExtensionDataRef<string, 'core.title', {}>
    +          | ConfigurableExtensionDataRef<JSX_2.Element, 'core.reactElement', {}>
    +          | ConfigurableExtensionDataRef<string, 'core.routing.path', {}>
    +          | ConfigurableExtensionDataRef<
    +              RouteRef_2<AnyRouteRefParams>,
    +              'core.routing.ref',
    +              {
    +                optional: true;
    +              }
    +            >,
    +          {
    +            singleton: false;
    +            optional: true;
    +          }
    +        >;
    +      };
    +      kind: 'page';
    +      name: undefined;
           params: {
             defaultPath?: [Error: `Use the 'path' param instead`];
             path: string;
    
  • plugins/devtools/report.api.md+18 1 modified
    @@ -6,6 +6,7 @@
     import { BackstagePlugin } from '@backstage/core-plugin-api';
     import { ElementType } from 'react';
     import { JSX as JSX_2 } from 'react/jsx-runtime';
    +import { ReactElement } from 'react';
     import { ReactNode } from 'react';
     import { RouteRef } from '@backstage/core-plugin-api';
     import { TabProps } from '@material-ui/core/Tab';
    @@ -28,7 +29,23 @@ export type DevToolsLayoutProps = {
     };
     
     // @public (undocumented)
    -export const DevToolsPage: () => JSX_2.Element;
    +export const DevToolsPage: ({ contents }: DevToolsPageProps) => JSX_2.Element;
    +
    +// @public (undocumented)
    +export interface DevToolsPageContent {
    +  // (undocumented)
    +  children: ReactElement;
    +  // (undocumented)
    +  path: string;
    +  // (undocumented)
    +  title: string;
    +}
    +
    +// @public (undocumented)
    +export interface DevToolsPageProps {
    +  // (undocumented)
    +  contents?: DevToolsPageContent[];
    +}
     
     // @public (undocumented)
     export const devToolsPlugin: BackstagePlugin<
    
  • plugins/devtools/src/alpha/plugin.tsx+31 6 modified
    @@ -21,6 +21,8 @@ import {
       ApiBlueprint,
       PageBlueprint,
       NavItemBlueprint,
    +  createExtensionInput,
    +  coreExtensionData,
     } from '@backstage/frontend-plugin-api';
     
     import { devToolsApiRef, DevToolsClient } from '../api';
    @@ -42,12 +44,35 @@ export const devToolsApi = ApiBlueprint.make({
     });
     
     /** @alpha */
    -export const devToolsPage = PageBlueprint.make({
    -  params: {
    -    path: '/devtools',
    -    routeRef: rootRouteRef,
    -    loader: () =>
    -      import('../components/DevToolsPage').then(m => <m.DevToolsPage />),
    +export const devToolsPage = PageBlueprint.makeWithOverrides({
    +  inputs: {
    +    contents: createExtensionInput(
    +      [
    +        coreExtensionData.reactElement,
    +        coreExtensionData.routePath,
    +        coreExtensionData.routeRef.optional(),
    +        coreExtensionData.title,
    +      ],
    +      {
    +        optional: true,
    +      },
    +    ),
    +  },
    +  factory(originalFactory, { inputs }) {
    +    return originalFactory({
    +      path: '/devtools',
    +      routeRef: rootRouteRef,
    +      loader: () => {
    +        const contents = inputs.contents.map(content => ({
    +          path: content.get(coreExtensionData.routePath),
    +          title: content.get(coreExtensionData.title),
    +          children: content.get(coreExtensionData.reactElement),
    +        }));
    +        return import('../components/DevToolsPage').then(m => (
    +          <m.DevToolsPage contents={contents} />
    +        ));
    +      },
    +    });
       },
     });
     
    
  • plugins/devtools/src/components/DefaultDevToolsPage/DefaultDevToolsPage.tsx+14 3 modified
    @@ -18,15 +18,17 @@ import {
       devToolsConfigReadPermission,
       devToolsInfoReadPermission,
     } from '@backstage/plugin-devtools-common';
    +
    +import { ConfigContent } from '../Content';
     import { devToolsTaskSchedulerReadPermission } from '@backstage/plugin-devtools-common/alpha';
    -import { ConfigContent } from '../Content/ConfigContent';
     import { DevToolsLayout } from '../DevToolsLayout';
    -import { InfoContent } from '../Content/InfoContent';
    +import { InfoContent } from '../Content';
     import { RequirePermission } from '@backstage/plugin-permission-react';
     import { ScheduledTasksContent } from '../Content/ScheduledTasksContent';
    +import { DevToolsPageProps } from '../DevToolsPage';
     
     /** @public */
    -export const DefaultDevToolsPage = () => (
    +export const DefaultDevToolsPage = ({ contents }: DevToolsPageProps) => (
       <DevToolsLayout>
         <DevToolsLayout.Route path="info" title="Info">
           <RequirePermission permission={devToolsInfoReadPermission}>
    @@ -43,5 +45,14 @@ export const DefaultDevToolsPage = () => (
             <ScheduledTasksContent />
           </RequirePermission>
         </DevToolsLayout.Route>
    +    {contents?.map((content, index) => (
    +      <DevToolsLayout.Route
    +        key={`extension-${index}`}
    +        path={content.path}
    +        title={content.title}
    +      >
    +        {content.children}
    +      </DevToolsLayout.Route>
    +    ))}
       </DevToolsLayout>
     );
    
  • plugins/devtools/src/components/DevToolsPage/DevToolsPage.tsx+19 2 modified
    @@ -16,9 +16,26 @@
     
     import { useOutlet } from 'react-router-dom';
     import { DefaultDevToolsPage } from '../DefaultDevToolsPage';
    +import { ReactElement } from 'react';
     
    -export const DevToolsPage = () => {
    +/**
    +  @public
    + */
    +export interface DevToolsPageProps {
    +  contents?: DevToolsPageContent[];
    +}
    +
    +/**
    +  @public
    + */
    +export interface DevToolsPageContent {
    +  title: string;
    +  path: string;
    +  children: ReactElement;
    +}
    +
    +export const DevToolsPage = ({ contents }: DevToolsPageProps) => {
       const outlet = useOutlet();
     
    -  return <>{outlet || <DefaultDevToolsPage />}</>;
    +  return <>{outlet || <DefaultDevToolsPage contents={contents} />}</>;
     };
    
  • plugins/devtools/src/components/DevToolsPage/index.ts+5 1 modified
    @@ -14,4 +14,8 @@
      * limitations under the License.
      */
     
    -export { DevToolsPage } from './DevToolsPage';
    +export {
    +  DevToolsPage,
    +  type DevToolsPageProps,
    +  type DevToolsPageContent,
    +} from './DevToolsPage';
    
  • plugins/devtools/src/components/index.ts+4 0 modified
    @@ -16,3 +16,7 @@
     
     export * from './Content';
     export * from './DevToolsLayout';
    +export {
    +  type DevToolsPageProps,
    +  type DevToolsPageContent,
    +} from './DevToolsPage';
    
  • yarn.lock+133 14 modified
    @@ -5444,12 +5444,14 @@ __metadata:
       resolution: "@backstage/plugin-catalog-unprocessed-entities@workspace:plugins/catalog-unprocessed-entities"
       dependencies:
         "@backstage/cli": "workspace:^"
    +    "@backstage/core-compat-api": "workspace:^"
         "@backstage/core-components": "workspace:^"
         "@backstage/core-plugin-api": "workspace:^"
         "@backstage/dev-utils": "workspace:^"
         "@backstage/errors": "workspace:^"
         "@backstage/frontend-plugin-api": "workspace:^"
         "@backstage/plugin-catalog-unprocessed-entities-common": "workspace:^"
    +    "@backstage/plugin-devtools-react": "workspace:^"
         "@material-ui/core": "npm:^4.9.13"
         "@material-ui/icons": "npm:^4.9.1"
         "@material-ui/lab": "npm:^4.0.0-alpha.60"
    @@ -5611,6 +5613,32 @@ __metadata:
       languageName: unknown
       linkType: soft
     
    +"@backstage/plugin-devtools-react@workspace:^, @backstage/plugin-devtools-react@workspace:plugins/devtools-react":
    +  version: 0.0.0-use.local
    +  resolution: "@backstage/plugin-devtools-react@workspace:plugins/devtools-react"
    +  dependencies:
    +    "@backstage/cli": "workspace:^"
    +    "@backstage/core-plugin-api": "workspace:^"
    +    "@backstage/frontend-plugin-api": "workspace:^"
    +    "@backstage/test-utils": "workspace:^"
    +    "@material-ui/core": "npm:^4.9.13"
    +    "@testing-library/jest-dom": "npm:^6.0.0"
    +    "@testing-library/react": "npm:^14.0.0"
    +    "@types/react": "npm:^18.0.0"
    +    react: "npm:^18.0.2"
    +    react-dom: "npm:^18.0.2"
    +    react-router-dom: "npm:^6.3.0"
    +  peerDependencies:
    +    "@types/react": ^17.0.0 || ^18.0.0
    +    react: ^17.0.0 || ^18.0.0
    +    react-dom: ^17.0.0 || ^18.0.0
    +    react-router-dom: ^6.3.0
    +  peerDependenciesMeta:
    +    "@types/react":
    +      optional: true
    +  languageName: unknown
    +  linkType: soft
    +
     "@backstage/plugin-devtools@workspace:^, @backstage/plugin-devtools@workspace:plugins/devtools":
       version: 0.0.0-use.local
       resolution: "@backstage/plugin-devtools@workspace:plugins/devtools"
    @@ -5623,6 +5651,7 @@ __metadata:
         "@backstage/errors": "workspace:^"
         "@backstage/frontend-plugin-api": "workspace:^"
         "@backstage/plugin-devtools-common": "workspace:^"
    +    "@backstage/plugin-devtools-react": "workspace:^"
         "@backstage/plugin-permission-react": "workspace:^"
         "@material-ui/core": "npm:^4.9.13"
         "@material-ui/icons": "npm:^4.9.1"
    @@ -20169,6 +20198,22 @@ __metadata:
       languageName: node
       linkType: hard
     
    +"@testing-library/dom@npm:^9.0.0":
    +  version: 9.3.4
    +  resolution: "@testing-library/dom@npm:9.3.4"
    +  dependencies:
    +    "@babel/code-frame": "npm:^7.10.4"
    +    "@babel/runtime": "npm:^7.12.5"
    +    "@types/aria-query": "npm:^5.0.1"
    +    aria-query: "npm:5.1.3"
    +    chalk: "npm:^4.1.0"
    +    dom-accessibility-api: "npm:^0.5.9"
    +    lz-string: "npm:^1.5.0"
    +    pretty-format: "npm:^27.0.2"
    +  checksum: 10/510da752ea76f4a10a0a4e3a77917b0302cf03effe576cd3534cab7e796533ee2b0e9fb6fb11b911a1ebd7c70a0bb6f235bf4f816c9b82b95b8fe0cddfd10975
    +  languageName: node
    +  linkType: hard
    +
     "@testing-library/jest-dom@npm:^6.0.0, @testing-library/jest-dom@npm:^6.6.3":
       version: 6.9.1
       resolution: "@testing-library/jest-dom@npm:6.9.1"
    @@ -20205,6 +20250,20 @@ __metadata:
       languageName: node
       linkType: hard
     
    +"@testing-library/react@npm:^14.0.0":
    +  version: 14.3.1
    +  resolution: "@testing-library/react@npm:14.3.1"
    +  dependencies:
    +    "@babel/runtime": "npm:^7.12.5"
    +    "@testing-library/dom": "npm:^9.0.0"
    +    "@types/react-dom": "npm:^18.0.0"
    +  peerDependencies:
    +    react: ^18.0.0
    +    react-dom: ^18.0.0
    +  checksum: 10/83359dcdf9eaf067839f34604e1a181cbc14fc09f3a07672403700fcc6a900c4b8054ad1114fc24b4b9f89d84e2a09e1b7c9afce2306b1d4b4c9e30eb1cb12de
    +  languageName: node
    +  linkType: hard
    +
     "@testing-library/react@npm:^16.0.0":
       version: 16.3.1
       resolution: "@testing-library/react@npm:16.3.1"
    @@ -24713,6 +24772,15 @@ __metadata:
       languageName: node
       linkType: hard
     
    +"aria-query@npm:5.1.3":
    +  version: 5.1.3
    +  resolution: "aria-query@npm:5.1.3"
    +  dependencies:
    +    deep-equal: "npm:^2.0.5"
    +  checksum: 10/e5da608a7c4954bfece2d879342b6c218b6b207e2d9e5af270b5e38ef8418f02d122afdc948b68e32649b849a38377785252059090d66fa8081da95d1609c0d2
    +  languageName: node
    +  linkType: hard
    +
     "aria-query@npm:5.3.0":
       version: 5.3.0
       resolution: "aria-query@npm:5.3.0"
    @@ -24729,7 +24797,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2":
    +"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2":
       version: 1.0.2
       resolution: "array-buffer-byte-length@npm:1.0.2"
       dependencies:
    @@ -25013,6 +25081,20 @@ __metadata:
       languageName: node
       linkType: hard
     
    +"async-function@npm:^1.0.0":
    +  version: 1.0.0
    +  resolution: "async-function@npm:1.0.0"
    +  checksum: 10/1a09379937d846f0ce7614e75071c12826945d4e417db634156bf0e4673c495989302f52186dfa9767a1d9181794554717badd193ca2bbab046ef1da741d8efd
    +  languageName: node
    +  linkType: hard
    +
    +"async-generator-function@npm:^1.0.0":
    +  version: 1.0.0
    +  resolution: "async-generator-function@npm:1.0.0"
    +  checksum: 10/3d49e7acbeee9e84537f4cb0e0f91893df8eba976759875ae8ee9e3d3c82f6ecdebdb347c2fad9926b92596d93cdfc78ecc988bcdf407e40433e8e8e6fe5d78e
    +  languageName: node
    +  linkType: hard
    +
     "async-lock@npm:^1.4.1":
       version: 1.4.1
       resolution: "async-lock@npm:1.4.1"
    @@ -28491,6 +28573,32 @@ __metadata:
       languageName: node
       linkType: hard
     
    +"deep-equal@npm:^2.0.5":
    +  version: 2.2.3
    +  resolution: "deep-equal@npm:2.2.3"
    +  dependencies:
    +    array-buffer-byte-length: "npm:^1.0.0"
    +    call-bind: "npm:^1.0.5"
    +    es-get-iterator: "npm:^1.1.3"
    +    get-intrinsic: "npm:^1.2.2"
    +    is-arguments: "npm:^1.1.1"
    +    is-array-buffer: "npm:^3.0.2"
    +    is-date-object: "npm:^1.0.5"
    +    is-regex: "npm:^1.1.4"
    +    is-shared-array-buffer: "npm:^1.0.2"
    +    isarray: "npm:^2.0.5"
    +    object-is: "npm:^1.1.5"
    +    object-keys: "npm:^1.1.1"
    +    object.assign: "npm:^4.1.4"
    +    regexp.prototype.flags: "npm:^1.5.1"
    +    side-channel: "npm:^1.0.4"
    +    which-boxed-primitive: "npm:^1.0.2"
    +    which-collection: "npm:^1.0.1"
    +    which-typed-array: "npm:^1.1.13"
    +  checksum: 10/1ce49d0b71d0f14d8ef991a742665eccd488dfc9b3cada069d4d7a86291e591c92d2589c832811dea182b4015736b210acaaebce6184be356c1060d176f5a05f
    +  languageName: node
    +  linkType: hard
    +
     "deep-equal@npm:~1.0.1":
       version: 1.0.1
       resolution: "deep-equal@npm:1.0.1"
    @@ -29686,7 +29794,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"es-get-iterator@npm:^1.0.2":
    +"es-get-iterator@npm:^1.0.2, es-get-iterator@npm:^1.1.3":
       version: 1.1.3
       resolution: "es-get-iterator@npm:1.1.3"
       dependencies:
    @@ -30700,6 +30808,7 @@ __metadata:
         "@backstage/plugin-catalog-import": "workspace:^"
         "@backstage/plugin-catalog-react": "workspace:^"
         "@backstage/plugin-catalog-unprocessed-entities": "workspace:^"
    +    "@backstage/plugin-devtools": "workspace:^"
         "@backstage/plugin-home": "workspace:^"
         "@backstage/plugin-kubernetes": "workspace:^"
         "@backstage/plugin-kubernetes-cluster": "workspace:^"
    @@ -32256,6 +32365,13 @@ __metadata:
       languageName: node
       linkType: hard
     
    +"generator-function@npm:^2.0.0":
    +  version: 2.0.1
    +  resolution: "generator-function@npm:2.0.1"
    +  checksum: 10/eb7e7eb896c5433f3d40982b2ccacdb3dd990dd3499f14040e002b5d54572476513be8a2e6f9609f6e41ab29f2c4469307611ddbfc37ff4e46b765c326663805
    +  languageName: node
    +  linkType: hard
    +
     "generic-names@npm:^2.0.1":
       version: 2.0.1
       resolution: "generic-names@npm:2.0.1"
    @@ -32303,21 +32419,24 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0":
    -  version: 1.3.0
    -  resolution: "get-intrinsic@npm:1.3.0"
    +"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0":
    +  version: 1.3.1
    +  resolution: "get-intrinsic@npm:1.3.1"
       dependencies:
    +    async-function: "npm:^1.0.0"
    +    async-generator-function: "npm:^1.0.0"
         call-bind-apply-helpers: "npm:^1.0.2"
         es-define-property: "npm:^1.0.1"
         es-errors: "npm:^1.3.0"
         es-object-atoms: "npm:^1.1.1"
         function-bind: "npm:^1.1.2"
    +    generator-function: "npm:^2.0.0"
         get-proto: "npm:^1.0.1"
         gopd: "npm:^1.2.0"
         has-symbols: "npm:^1.1.0"
         hasown: "npm:^2.0.2"
         math-intrinsics: "npm:^1.1.0"
    -  checksum: 10/6e9dd920ff054147b6f44cb98104330e87caafae051b6d37b13384a45ba15e71af33c3baeac7cb630a0aaa23142718dcf25b45cfdd86c184c5dcb4e56d953a10
    +  checksum: 10/bb579dda84caa4a3a41611bdd483dade7f00f246f2a7992eb143c5861155290df3fdb48a8406efa3dfb0b434e2c8fafa4eebd469e409d0439247f85fc3fa2cc1
       languageName: node
       linkType: hard
     
    @@ -34407,7 +34526,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5":
    +"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5":
       version: 3.0.5
       resolution: "is-array-buffer@npm:3.0.5"
       dependencies:
    @@ -34880,7 +34999,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"is-regex@npm:^1.2.1":
    +"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1":
       version: 1.2.1
       resolution: "is-regex@npm:1.2.1"
       dependencies:
    @@ -34929,7 +35048,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"is-shared-array-buffer@npm:^1.0.4":
    +"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.4":
       version: 1.0.4
       resolution: "is-shared-array-buffer@npm:1.0.4"
       dependencies:
    @@ -44711,7 +44830,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4":
    +"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4":
       version: 1.5.4
       resolution: "regexp.prototype.flags@npm:1.5.4"
       dependencies:
    @@ -46279,7 +46398,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"side-channel@npm:^1.1.0":
    +"side-channel@npm:^1.0.4, side-channel@npm:^1.1.0":
       version: 1.1.0
       resolution: "side-channel@npm:1.1.0"
       dependencies:
    @@ -50503,7 +50622,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
    +"which-boxed-primitive@npm:^1.0.2, which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
       version: 1.1.1
       resolution: "which-boxed-primitive@npm:1.1.1"
       dependencies:
    @@ -50537,7 +50656,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"which-collection@npm:^1.0.2":
    +"which-collection@npm:^1.0.1, which-collection@npm:^1.0.2":
       version: 1.0.2
       resolution: "which-collection@npm:1.0.2"
       dependencies:
    @@ -50559,7 +50678,7 @@ __metadata:
       languageName: node
       linkType: hard
     
    -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19, which-typed-array@npm:^1.1.2":
    +"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19, which-typed-array@npm:^1.1.2":
       version: 1.1.19
       resolution: "which-typed-array@npm:1.1.19"
       dependencies:
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.