CVE-2021-43783 (TypeScript, published 2021-11-29T20:15Z)
Description: value: @backstage/plugin-scaffolder-backend is the backend for the default Backstage software templates. In affected versions a malicious actor with write access to a registered scaffolder template is able to manipulate the template in a way that writes files to arbitrary paths on the scaffolder-backend host instance. This vulnerability can in some situation also be exploited through user input when executing a template, meaning you do not need write access to the templates. This method will not allow the attacker to control the contents of the injected file however, unless the template is also crafted in a specific way that gives control of the file contents. This vulnerability is fixed in version `0.15.14` of the `@backstage/plugin-scaffolder-backend`. This attack is mitigated by restricting access and requiring reviews when registering or modifying scaffolder templates.
Severity: MEDIUM (CVSS v3 base score: 8.5)
Weakness: CWE-22 \u2014 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.
Repository: https://github.com/backstage/backstage
Files changed:
.changeset/serious-pens-tease.md
plugins/scaffolder-backend/.eslintrc.js
plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts
plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts
plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts
plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts
plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts
Commit message: scaffolder-backend: removed all usaged and prevent new usage of path.resolve

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>


Vulnerable code:
import { resolve as resolvePath } from 'path';
        resolvePath(ctx.workspacePath, 'catalog-info.yaml'),
import { relative, resolve } from 'path';
      const res = resolve(dir, subdir);
import * as path from 'path';
import { resolve as resolvePath, extname } from 'path';
      const templateDir = resolvePath(workDir, 'template');
        const outputPath = resolvePath(outputDir, localOutputPath);
          const inputFilePath = resolvePath(templateDir, location);
import path from 'path';
          const absPath = path.resolve(fileRoot, filePath);

Fixed code:
---
'@backstage/plugin-scaffolder-backend': patch
---

Removed all usages of `path.resolve` in order to ensure that template paths are resolved in a safe way.
const parent = require('@backstage/cli/config/eslint.backend');

    // Usage of path.resolve is extra sensitive in the scaffolder, so forbid it in non-test code
    'no-restricted-imports': [
      'error',
      {
        ...parent.rules['no-restricted-imports'][1],
        paths: [
          {
            name: 'path',
            importNames: ['resolve'],
            message:
              'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues',
          },
        ],
      },
    ],
    'no-restricted-syntax': parent.rules['no-restricted-syntax'].concat([
      {
        message:
          'Do not use path.resolve, use `resolveSafeChildPath` from `@backstage/backend-common` instead as it prevents security issues',
        selector:
          'MemberExpression[object.name="path"][property.name="resolve"]',
      },
    ]),
  overrides: [
    {
      files: ['*.test.*', 'src/setupTests.*', 'dev/**'],
      rules: {
        'no-restricted-imports': parent.rules['no-restricted-imports'],
      },
    },
  ],
import { resolveSafeChildPath } from '@backstage/backend-common';
        resolveSafeChildPath(ctx.workspacePath, 'catalog-info.yaml'),
import { relative, join } from 'path';
      const res = join(dir, subdir);
import path from 'path';
import { extname } from 'path';
      const templateDir = resolveSafeChildPath(workDir, 'template');
        const outputPath = resolveSafeChildPath(outputDir, localOutputPath);
          const inputFilePath = resolveSafeChildPath(templateDir, location);
          const absPath = resolveSafeChildPath(fileRoot, filePath);