CVE-2021-23423 (Python, published 2021-08-16T08:15Z)
Description: value: This affects the package bikeshed before 3.0.0. This can occur when an untrusted source file containing include, include-code or include-raw block is processed. The contents of arbitrary files could be disclosed in the HTML output.
Severity: MEDIUM (CVSS v3 base score: 7.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/tabatkins/bikeshed
Files changed:
bikeshed/InputSource.py
bikeshed/Spec.py
bikeshed/cli.py
bikeshed/config/main.py
bikeshed/config/retrieve.py
bikeshed/constants.py
bikeshed/extensions.py
bikeshed/inlineTags/__init__.py
Commit message: Prevent escaping the source doc's folder, or running arbitrary code, without explicit opt-in at the command-line.


Vulnerable code:
    def __new__(cls, sourceName: str):
            return StdinInputSource(sourceName)
            return UrlInputSource(sourceName)
        return FileInputSource(sourceName)
    def __init__(self, sourceName: str):
        return FileInputSource(os.path.join(self.directory(), relativePath))
        self.inputSource = InputSource(inputFilename)
    return os.path.join(startPath, *pathSegs)
def retrieveBoilerplateFile(doc, name, group=None, status=None, error=True):
    searchLocally = doc.md.localBoilerplate[name]
        sources.append(InputSource(boilerplatePath(group, statusFile)))
        sources.append(InputSource(boilerplatePath(group, genericFile)))
        sources.append(InputSource(boilerplatePath(megaGroup, statusFile)))
        sources.append(InputSource(boilerplatePath(megaGroup, genericFile)))
    sources.append(InputSource(boilerplatePath(statusFile)))
    sources.append(InputSource(boilerplatePath(genericFile)))
    code = config.retrieveBoilerplateFile(doc, "bs-extensions")

Fixed code:
from . import config
    def __new__(cls, sourceName: str, **kwargs):
            return StdinInputSource(sourceName, **kwargs)
            return UrlInputSource(sourceName, **kwargs)
        return FileInputSource(sourceName, **kwargs)
    def __init__(self, sourceName: str, *, chroot: bool, chrootPath: Optional[str] = None):
        self.chrootPath = chrootPath
        if chroot and self.chrootPath is None:
            self.chrootPath = self.directory()
        if self.chrootPath is not None:
            self.sourceName = config.chrootPath(self.chrootPath, self.sourceName)

        return FileInputSource(os.path.join(self.directory(), relativePath), chroot=False, chrootPath=self.chrootPath)
        self.inputSource = InputSource(inputFilename, chroot=constants.chroot)
    argparser.add_argument(
        "--allow-nonlocal-files",
        dest="allowNonlocalFiles",
        action="store_true",
        help="Allows Bikeshed to see/include files from folders higher than the one your source document is in."
    )
    argparser.add_argument(
        "--allow-execute",
        dest="allowExecute",
        action="store_true",
        help="Allow some features to execute arbitrary code from outside the Bikeshed codebase."
    )
    constants.chroot = not options.allowNonlocalFiles
    constants.executeCode = options.allowExecute
from .. import constants
from .. import messages

    path = os.path.join(startPath, *pathSegs)
    return path


def chrootPath(chrootPath, path):
    chrootPath = os.path.abspath(chrootPath)
    path = os.path.abspath(path)
    if not path.startswith(chrootPath):
        messages.die(f"Attempted to access a file ({path}) outside the source document's directory ({chrootPath}). See --allow-nonlocal-files.")
        raise Exception()
    else:
        return path

def retrieveBoilerplateFile(doc, name, group=None, status=None, error=True, allowLocal=True):
    searchLocally = allowLocal and doc.md.localBoilerplate[name]
        sources.append(InputSource(boilerplatePath(group, statusFile), chroot=False))
        sources.append(InputSource(boilerplatePath(group, genericFile), chroot=False))
        sources.append(InputSource(boilerplatePath(megaGroup, statusFile), chroot=False))
        sources.append(InputSource(boilerplatePath(megaGroup, genericFile), chroot=False))
    sources.append(InputSource(boilerplatePath(statusFile), chroot=False))
    sources.append(InputSource(boilerplatePath(genericFile), chroot=False))
chroot = True
executeCode = False
from . import constants
    code = config.retrieveBoilerplateFile(doc, "bs-extensions", allowLocal=constants.executeCode)
from .. import constants
        if not constants.executeCode:
            die("Found an inline code tag, but arbitrary code execution isn't allowed. See the --allow-execute flag.")
            return