CVE-2021-23422 (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 Inline Tag Command metadata is processed. When an arbitrary OS command is executed, the command output would be included in the HTML output.
Severity: MEDIUM (CVSS v3 base score: 7.8)
Weakness: CWE-78 \u2014 Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
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