CVE-2021-32662 (TypeScript, published 2021-06-03T22:15Z)
Description: value: Backstage is an open platform for building developer portals, and techdocs-common contains common functionalities for Backstage's TechDocs. In `@backstage/techdocs-common` versions prior to 0.6.3, a malicious actor could read sensitive files from the environment where TechDocs documentation is built and published by setting a particular path for `docs_dir` in `mkdocs.yml`. These files would then be available over the TechDocs backend API. This vulnerability is mitigated by the fact that an attacker would need access to modify the `mkdocs.yml` in the documentation source code, and would also need access to the TechDocs backend API. The vulnerability is patched in the `0.6.3` release of `@backstage/techdocs-common`.
Severity: LOW (CVSS v3 base score: 6.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/gold-kings-jog.md
packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_invalid_doc_dir.yml
packages/techdocs-common/src/stages/generate/helpers.test.ts
packages/techdocs-common/src/stages/generate/helpers.ts
packages/techdocs-common/src/stages/generate/techdocs.ts
Commit message: Add validation to prevent docs_dir from being an absolute path

* Adds a new validation function to helpers to prevent generation when mkdocs.yml is not present or is invalid
* Handles vulnerability where docs_dir can be put in as an absolute path which copies and exposes the files from that absolute path in the file system

Signed-off-by: Jussi Hallila <jussi@hallila.com>


Vulnerable code:
        path.join(inputDir, 'mkdocs.yml'),

Fixed code:
---
'@backstage/techdocs-common': patch
---

Adding validation to mkdocs.yml parsing to prevent directory tree traversing
site_name: Test site name
site_description: Test site description
docs_dir: /etc
  validateMkdocsYaml,
const mkdocsYmlWithInvalidDocDir = fs.readFileSync(
  resolvePath(__filename, '../__fixtures__/mkdocs_invalid_doc_dir.yml'),
);

  describe('validateMkdocsYaml', () => {
    beforeEach(() => {
      mockFs({
        '/mkdocs.yml': mkdocsYml,
        '/mkdocs_with_repo_url.yml': mkdocsYmlWithRepoUrl,
        '/mkdocs_invalid_doc_dir.yml': mkdocsYmlWithInvalidDocDir,
      });
    });

    afterEach(() => {
      mockFs.restore();
    });

    it('should return true on when no docs_dir present', async () => {
      await expect(validateMkdocsYaml('/mkdocs.yml')).resolves.toBeUndefined();
    });

    it('should return false on absolute doc_dir path', async () => {
      await expect(
        validateMkdocsYaml('/mkdocs_invalid_doc_dir.yml'),
      ).rejects.toThrow();
    });
  });
import { isAbsolute, normalize } from 'path';
/**
 * Validating mkdocs config file for incorrect/insecure values
 * Throws on invalid configs
 *
 * @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site
 */
export const validateMkdocsYaml = async (mkdocsYmlPath: string) => {
  let mkdocsYmlFileString;
  try {
    mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8');
  } catch (error) {
    throw new Error(
      `Could not read MkDocs YAML config file ${mkdocsYmlPath} before for validation: ${error.message}`,
    );
  }

  const mkdocsYml: any = yaml.load(mkdocsYmlFileString);
  if (mkdocsYml.docs_dir && isAbsolute(normalize(mkdocsYml.docs_dir))) {
    throw new Error(
      "docs_dir configuration value in mkdocs can't be an absolute directory path for security reasons. Use relative paths instead which are resolved relative to your mkdocs.yml file location.",
    );
  }
};

  validateMkdocsYaml,
    const mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml');
        mkdocsYmlPath,
    await validateMkdocsYaml(mkdocsYmlPath);