TechDocs mkdocs.yml path traversal
Description
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.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
@backstage/techdocs-commonnpm | < 0.6.3 | 0.6.3 |
Affected products
1Patches
18cefadca04cbAdd validation to prevent docs_dir from being an absolute path
5 files changed · +66 −1
.changeset/gold-kings-jog.md+5 −0 added@@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Adding validation to mkdocs.yml parsing to prevent directory tree traversing
packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_invalid_doc_dir.yml+3 −0 added@@ -0,0 +1,3 @@ +site_name: Test site name +site_description: Test site description +docs_dir: /etc
packages/techdocs-common/src/stages/generate/helpers.test.ts+28 −0 modified@@ -27,6 +27,7 @@ import { isValidRepoUrlForMkdocs, patchMkdocsYmlPreBuild, storeEtagMetadata, + validateMkdocsYaml, } from './helpers'; const mockEntity = { @@ -43,6 +44,9 @@ const mkdocsYml = fs.readFileSync( const mkdocsYmlWithRepoUrl = fs.readFileSync( resolvePath(__filename, '../__fixtures__/mkdocs_with_repo_url.yml'), ); +const mkdocsYmlWithInvalidDocDir = fs.readFileSync( + resolvePath(__filename, '../__fixtures__/mkdocs_invalid_doc_dir.yml'), +); const mockLogger = getVoidLogger(); const rootDir = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; @@ -300,4 +304,28 @@ describe('helpers', () => { expect(json.etag).toBe('etag123abc'); }); }); + + 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(); + }); + }); });
packages/techdocs-common/src/stages/generate/helpers.ts+25 −0 modified@@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { spawn } from 'child_process'; +import { isAbsolute, normalize } from 'path'; import fs from 'fs-extra'; import yaml from 'js-yaml'; import { PassThrough, Writable } from 'stream'; @@ -138,6 +139,30 @@ export const getRepoUrlFromLocationAnnotation = ( return undefined; }; +/** + * 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.", + ); + } +}; + /** * Update the mkdocs.yml file before TechDocs generator uses it to generate docs site. *
packages/techdocs-common/src/stages/generate/techdocs.ts+5 −1 modified@@ -24,6 +24,7 @@ import { patchMkdocsYmlPreBuild, runCommand, storeEtagMetadata, + validateMkdocsYaml, } from './helpers'; import { GeneratorBase, GeneratorRunOptions } from './types'; @@ -79,14 +80,17 @@ export class TechdocsGenerator implements GeneratorBase { // TODO: In future mkdocs.yml can be mkdocs.yaml. So, use a config variable here to find out // the correct file name. // Do some updates to mkdocs.yml before generating docs e.g. adding repo_url + const mkdocsYmlPath = path.join(inputDir, 'mkdocs.yml'); if (parsedLocationAnnotation) { await patchMkdocsYmlPreBuild( - path.join(inputDir, 'mkdocs.yml'), + mkdocsYmlPath, this.logger, parsedLocationAnnotation, ); } + await validateMkdocsYaml(mkdocsYmlPath); + // Directories to bind on container const mountDirs = { [inputDir]: '/input',
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
5- github.com/advisories/GHSA-pgf8-28gg-vpr6ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-32662ghsaADVISORY
- github.com/backstage/backstage/commit/8cefadca04cbf01d0394b0cb1983247e5f1d6208ghsax_refsource_MISCWEB
- github.com/backstage/backstage/releases/tag/release-2021-05-27ghsax_refsource_MISCWEB
- github.com/backstage/backstage/security/advisories/GHSA-pgf8-28gg-vpr6ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.