CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-125123

CVE-2026-125123: Improper Output Neutralization and CI Workflow Command Injection in compose-lint CLI Parser

Alon Barad
Alon Barad
Software Engineer

Nov 10, 2026·10 min read·0 visits

Executive Summary (TL;DR)

compose-lint 0.31.0 fails to sanitize CLI argument parsing errors written to stderr by Python's argparse, allowing attackers to inject CI workflow commands via maliciously named file paths in pull requests when invoked without the '--' separator. Fixed in version 0.32.0.

An improper output neutralization vulnerability (CWE-117 / CWE-150) exists in compose-lint version 0.31.0. When compose-lint is invoked on untrusted file paths without a preceding '--' option delimiter, Python's standard argparse module rejects malformed or dash-prefixed paths and writes them directly to standard error without passing through the application's output sanitizer. An attacker submitting a pull request containing crafted directory or file names with carriage returns or Unicode whitespace can inject arbitrary CI workflow commands into GitHub Actions or Azure Pipelines job logs.

Vulnerability Overview

compose-lint is a static analysis security linter designed to identify misconfigurations in Docker Compose files prior to deployment. It is commonly deployed within Continuous Integration (CI) pipelines, such as GitHub Actions and Azure Pipelines, or executed via pre-commit hooks to validate untrusted code changes submitted in pull requests. Because CI runners parse standard output (stdout) and standard error (stderr) streams for inline workflow control sequences, tools operating on untrusted repository contents must neutralize control characters and command syntax before emitting diagnostic messages.

In compose-lint version 0.31.0, an incomplete output sanitization implementation left error messages generated by the command-line interface (CLI) argument parser unescaped. While version 0.31.0 introduced an output sanitizer (src/compose_lint/_output.py) to neutralize workflow commands embedded inside Docker Compose file contents, this sanitization only applied to text routed through the internal emit() function and structured report formatters. Standard error output generated directly by Python's argparse.ArgumentParser bypassed this sanitizer.

Classified under CWE-117 (Improper Output Neutralization for Logs) and CWE-150 (Improper Neutralization of Escape, Meta, or Control Sequences), this flaw allows untrusted file paths to trigger CI runner directives. When a CI pipeline passes changed file paths to compose-lint without an explicit -- option-terminating delimiter, paths beginning with dashes (- or --) are interpreted by argparse as CLI flags. When argparse rejects these unrecognized flags, it writes the raw, unsanitized input string directly to sys.stderr.

In addition to the argparse bypass, the version 0.31.0 sanitizer exhibited three secondary filter evasion flaws: incomplete handling of Unicode whitespace prefixes preceding GitHub Actions :: command tokens, failure to neutralize Azure Pipelines ##vso[ command tokens, and unescaped ##[ sequences within serialized JSON and SARIF output streams.

Root Cause Analysis

The root cause of CVE-2026-125123 lies in the interaction between Python's standard argparse error handling, line-ending normalization, and the stream parsing mechanics of CI runner processes. Both GitHub Actions and Azure Pipelines runners monitor step execution streams (stdout and stderr) for line-anchored or inline command tokens, such as ::notice::, ::warning::, ::error::, ##[command], and ##vso[task.setvariable]. If an attacker can position these tokens at the beginning of a parsed line—or inline for ##vso[—the runner executes the corresponding directive.

In compose-lint 0.31.0, the CLI entrypoint instantiated a standard argparse.ArgumentParser object. When ArgumentParser.parse_args() encounters an unrecognized option string (such as a directory named --malicious), it invokes self.error(), which calls self.print_usage(sys.stderr) and self.exit(2, message). The ArgumentParser._print_message() method writes the error string—including the verbatim rejected argument—directly to sys.stderr.write(), completely bypassing compose-lint's _output.py module.

To position a GitHub Actions ::command:: token at the start of a line inside an argparse error message (which normally prefixes output with compose-lint: error: unrecognized arguments: ), an attacker can embed a lone carriage return (\r) or newline (\n) within the file path or option value. Because the .NET runtime underlying the GitHub Actions runner treats a standalone carriage return (\r) as a valid line break when reading process streams, everything immediately following \r is evaluated as the beginning of a new line.

Furthermore, even for output that passed through _output.py in version 0.31.0, the sanitization regular expressions did not match the full parsing behavior of the CI runner. Specifically, the GitHub Actions runner invokes .NET's String.TrimStart() before checking for :: workflow commands, which strips all Unicode characters categorized as whitespace (such as non-breaking space \xa0, ideographic space \u3000, and em-space \u2003). In contrast, compose-lint 0.31.0 used the regular expression ^([ \t]*):(?=:) to detect line-leading colons, matching only ASCII spaces and horizontal tabs. Consequently, prefixing ::warning:: with \xa0 bypassed the regex while still being trimmed and executed by the runner.

Code Analysis

The remediation in commit 542e63126f795564a65c63887ea0dc558a500688 addresses both the argparse output bypass in src/compose_lint/cli.py and the incomplete token neutralization in src/compose_lint/_output.py.

First, to ensure that all CLI parser output passes through the sanitization pipeline, compose-lint replaced direct usage of argparse.ArgumentParser with a custom subclass _Parser in src/compose_lint/cli.py. This subclass overrides print_usage, print_help, and exit to route all messages through emit_block() and sanitize():

# src/compose_lint/cli.py
class _Parser(argparse.ArgumentParser):
    """argparse with its own output routed through the sanitizer."""
 
    def print_usage(self, file: SupportsWrite[str] | None = None) -> None:
        # Route usage output through the custom _emit handler
        self._emit(self.format_usage(), file)
 
    def print_help(self, file: SupportsWrite[str] | None = None) -> None:
        # Route help output through the custom _emit handler
        self._emit(self.format_help(), file)
 
    def exit(self, status: int = 0, message: str | None = None) -> NoReturn:
        if message:
            # Intercept error messages (whichnormally go straight to sys.stderr)
            # and pass them through emit_block() for sanitization
            emit_block(message)
        sys.exit(status)
 
    @staticmethod
    def _emit(text: str, file: SupportsWrite[str] | None) -> None:
        if file is sys.stderr:
            emit_block(text)
        else:
            (sys.stdout if file is None else file).write(sanitize(text))

Second, the patch updates src/compose_lint/_output.py to close the regular expression discrepancies regarding Unicode whitespace, Azure Pipelines ##vso[ tokens, and serialized JSON/SARIF output:

# src/compose_lint/_output.py
 
# Before (v0.31.0):
# _LEGACY_COMMAND = re.compile(r"##\[")
# _LINE_COMMAND = re.compile(r"^([ \t]*):(?=:)", re.MULTILINE)
 
# After (v0.32.0):
# Match both legacy GitHub Actions '##[' and Azure Pipelines '##vso[' case-insensitively
_LEGACY_COMMAND = re.compile(r"##(vso)?\[", re.IGNORECASE)
 
# Match any non-newline whitespace character (including Unicode whitespace like \xa0)
# at the start of a line before '::'
_LINE_COMMAND = re.compile(r"^([^\S\n]*):(?=:)", re.MULTILINE)
 
def _defuse_commands(text: str) -> str:
    # Replace '[' with '\u005b' and ':' with '\u003a' to break runner token parsing
    text = _LEGACY_COMMAND.sub(lambda m: "##" + (m.group(1) or "") + "\\u005b", text)
    return _LINE_COMMAND.sub("\\1\\\\u003a", text)
 
def defuse_json(document: str) -> str:
    """Break every CI command opener inside serialized JSON, keeping it valid."""
    # In JSON strings, replacing '[' with '\u005b' produces an identical decoded string
    # for JSON parsers while preventing raw stdout stream scanners from matching '##['
    return _LEGACY_COMMAND.sub(
        lambda m: "##" + (m.group(1) or "") + "\\u005b", document
    )

> [!NOTE] > The defuse_json() function leverages JSON's native Unicode escape syntax (\u005b for [). Because json.dumps() does not escape [ by default, raw ##[ or ##vso[ sequences inside JSON fields on stdout would otherwise be interpreted by the CI runner before any downstream JSON consumer parsed the document. Replacing [ with \u005b inside the serialized JSON string neutralizes the token in the raw character stream while preserving the exact semantic value when parsed by a compliant JSON or SARIF reader.

An assessment of the fix in commit 542e63126f795564a65c63887ea0dc558a500688 indicates that the remediation is complete. Because argparse propagates the parent parser's class to subparsers automatically (or via explicit parser_class), all CLI argument validation failures now pass through emit_block(), which normalizes control characters (including \r) and defuses command prefixes prior to writing to sys.stderr.

Exploitation

Exploiting CVE-2026-125123 requires that a target repository's CI workflow or custom script invokes compose-lint version 0.31.0 on untrusted file paths or arguments without preceding positional path arguments with the -- option delimiter. For example, a custom GitHub Actions workflow step that runs compose-lint $(git diff --name-only origin/main...HEAD) expands modified file names directly as CLI arguments.

An attacker with permissions to open a pull request against the repository can create a directory or file whose name begins with -- and embeds either a carriage return (\r) followed by a :: workflow command, a newline followed by Unicode whitespace (\xa0) and ::, or an inline ##[ / ##vso[ directive. Linux and macOS filesystems permit arbitrary byte sequences in file and directory names except for the null byte (\0) and path separator (/).

The regression test suite introduced in commit 542e63126f795564a65c63887ea0dc558a500688 demonstrates the exact payload structures used to trigger the vulnerability through argparse:

# Excerpt from tests/test_workflow_command_injection.py
@pytest.mark.parametrize(
    "argv",
    [
        ["--x\r::notice::cl-p4-residue-marker/compose.yml"],
        ["--format", "x\r::notice::cl-p4-residue-marker", "compose.yml"],
    ],
    ids=["unrecognized-argument", "invalid-choice"],
)
def test_a_carriage_return_in_an_argument_argparse_echoes(
    tmp_path: Path, argv: list[str]
) -> None:
    (tmp_path / "compose.yml").write_text(
        "services:\n  web:\n    image: nginx:1.27\n", encoding="utf-8"
    )
    result = subprocess.run(
        [sys.executable, "-m", "compose_lint", *argv],
        cwd=tmp_path,
        capture_output=True,
        env=cli_env(PYTHONPATH=str(REPO_ROOT / "src"), NO_COLOR="1"),
        timeout=120,
    )
    assert result.returncode == 2

When compose-lint 0.31.0 processes the argument --x\r::notice::cl-p4-residue-marker/compose.yml, argparse rejects it with exit code 2 and outputs:

usage: compose-lint [-h] [--version] ...
compose-lint: error: unrecognized arguments: --x
::notice::cl-p4-residue-marker/compose.yml

When the GitHub Actions runner reads stderr, the byte terminates the line compose-lint: error: unrecognized arguments: --x, placing ::notice::cl-p4-residue-marker/compose.yml at column 0 of the subsequent line. Similarly, passing --##vso[task.x]marker or --##[warning]marker/compose.yml causes the inline token to be echoed directly into the runner log stream.

Impact Assessment

The security impact of CVE-2026-125123 is constrained to CI log manipulation and workflow command injection within non-default compose-lint integrations. The vulnerability is scored at 2.3 (Low) under CVSS v4.0 (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N). The Attack Requirements (AT:P) metric reflects that exploitation depends on a specific deployment pattern where untrusted paths are passed to the CLI without --.

Importantly, two common deployment methods are unaffected even when running version 0.31.0:

  • Official GitHub Action: The compose-lint GitHub Action (action.yml) wraps execution inside a randomized ::stop-commands::<token> block and enforces argument separation, preventing the runner from interpreting workflow commands during linter execution.
  • Default Pre-Commit Hook: The official .pre-commit-hooks.yaml definition includes -- prior to passing staged file paths, ensuring argparse treats all paths as positional arguments rather than options.

In vulnerable custom CI steps or Azure Pipelines tasks, an attacker can forge build annotations (::notice::, ::warning::, ::error::), mask arbitrary strings in logs (::add-mask::), or invoke runner commands such as ##vso[task.setvariable] in Azure Pipelines to modify pipeline variables for subsequent steps within the same job. Note that in GitHub Actions, pull requests from forked repositories typically run under restricted pull_request event tokens with read-only permissions, and modern runners disable ::set-env:: and ::add-path:: workflow commands in favor of environment files, limiting the potential for direct code execution in standard GitHub Actions environments.

Remediation

To remediate CVE-2026-125123, upgrade compose-lint to version 0.32.0 or later. Version 0.32.0 routes all argparse output through the output sanitizer, escapes carriage returns, expands line-prefix matching to cover all Unicode whitespace characters, and neutralizes ##[ and ##vso[ sequences across text, JSON, and SARIF output formats.

Update compose-lint across package managers, Docker containers, or pre-commit configurations as follows:

# Python / pip
pip install --upgrade "compose-lint>=0.32.0"
 
# Docker
docker pull composelint/compose-lint:0.32.0

For repositories using pre-commit, update the rev tag in .pre-commit-config.yaml:

repos:
  - repo: https://github.com/tmatens/compose-lint
    rev: v0.32.0
    hooks:
      - id: compose-lint

As a defense-in-depth measure and workaround for environments where immediate upgrading is not feasible, always include the -- option-terminating delimiter before passing dynamic file paths or glob expansions to compose-lint in shell scripts or CI workflows:

# Vulnerable invocation when $CHANGED_FILES contains paths starting with '-'
compose-lint $CHANGED_FILES
 
# Hardened invocation forcing all subsequent arguments to be treated as positional paths
compose-lint -- $CHANGED_FILES

Additionally, if annotation spoofing via third-party GitHub Actions problem matchers (such as those registered by actions/setup-python, which remain active even during stop-commands blocks) is a concern in untrusted PR workflows, explicitly remove active problem matchers prior to invoking linters:

- name: Remove python problem matcher
  run: echo "::remove-matcher owner=python::"

Official Patches

tmatenscompose-lint v0.32.0 Release
tmatensPull Request #945 fixing CLI argument parser and output sanitization
GitHubGitHub Security Advisory GHSA-8v59-qmwv-4p6q

Fix Analysis (1)

Technical Appendix

CVSS Score
2.3/ 10
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N

Affected Systems

compose-lint

Affected Versions Detail

Product
Affected Versions
Fixed Version
compose-lint
tmatens
= 0.31.00.32.0
AttributeDetail
CVE IDCVE-2026-125123 (GHSA-8v59-qmwv-4p6q)
Weakness ClassCWE-117 (Improper Output Neutralization for Logs) / CWE-150
CVSS v4.0 Score2.3 Low (CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:N/VI:L/VA:N/SC:N/SI:L/SA:N)
Affected Componentcompose-lint CLI Argument Parser (src/compose_lint/cli.py & _output.py)
Affected Version0.31.0 (Fixed in 0.32.0)
Attack VectorNetwork (Crafted file paths in Pull Requests passed to CLI without '--')
ImpactCI Workflow Command Injection in GitHub Actions / Azure Pipelines logs
CISA KEV StatusNot Listed

MITRE ATT&CK Mapping

T1059Command and Scripting Interpreter
Execution
T1562.006Impair Defenses: Indicator Blocking
Defense Evasion
T1036Masquerading
Defense Evasion
T1195.002Supply Chain Compromise: Compromise Software Supply Chain
Initial Access
CWE-117
Improper Output Neutralization for Logs

The software does not neutralize or incorrectly neutralizes output that is written to logs, allowing an attacker to forge log entries or inject malicious content into log parsers and CI runner streams.

Known Exploits & Detection

GitHubOfficial regression tests demonstrating CLI argument injection with carriage returns, Unicode whitespace, and Azure Pipelines tokens.

Vulnerability Timeline

compose-lint 0.31.0 released with initial workflow command sanitization.
2026-09-27
Fix authored and merged in Pull Request #945 (commit 542e63126f795564a65c63887ea0dc558a500688).
2026-09-28
compose-lint 0.32.0 released with complete output escaping across argparse, JSON, SARIF, and Unicode whitespace routes.
2026-09-28
GitHub Security Advisory GHSA-8v59-qmwv-4p6q and CVE-2026-125123 published.
2026-11-10

References & Sources

  • [1]GitHub Security Advisory GHSA-8v59-qmwv-4p6q
  • [2]compose-lint Pull Request #945
  • [3]Fix Commit 542e63126f795564a65c63887ea0dc558a500688
  • [4]compose-lint v0.32.0 Release Notes
  • [5]CVE.org Record: CVE-2026-125123
  • [6]NVD Detail: CVE-2026-125123

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•about 1 hour ago•CVE-2026-125116
4.8

CVE-2026-125116: Type Confusion in Mongoose Discriminator Resolution

CVE-2026-125116 (GHSA-wr5m-5jph-8m96) is a type confusion vulnerability (CWE-843) in Mongoose, the MongoDB object modeling library for Node.js. When resolving discriminator models or schemas via user-supplied discriminator keys (defaulting to `__t`), Mongoose performs direct property indexing on plain JavaScript registry objects without verifying own-property ownership. Supplying inherited `Object.prototype` property names such as `'constructor'` causes Mongoose to resolve built-in functions rather than valid Mongoose constructors, resulting in unhandled runtime exceptions, schema validation bypasses, and persistent collection query denial of service.

Alon Barad
Alon Barad
3 views•11 min read
•about 2 hours ago•CVE-2026-125119
7.5

CVE-2026-125119: Connection-Level Filter Sanitization Bypass in Mongoose ODM

A flaw in Mongoose's query condition casting pipeline causes the `sanitizeFilter` configuration option to be silently ignored when defined at the connection level via `mongoose.connect()` or `mongoose.createConnection()`. Because internal query methods inspect `Connection.options` rather than `Connection.config`, applications relying on connection-scoped filter sanitization remain vulnerable to NoSQL query selector injection (CWE-943), permitting authentication bypass and unauthorized data access across Mongoose versions 6.x through 9.x.

Amit Schendel
Amit Schendel
3 views•9 min read
•about 3 hours ago•CVE-2026-125110
5.3

CVE-2026-125110: Path Canonicalization Mismatch and Security Header Bypass in Rack::Static

CVE-2026-125110 is a path canonicalization mismatch vulnerability in Rack (specifically within the Rack::Static middleware component). When serving static files, Rack normalizes the requested URL path to resolve the physical file on disk, but evaluates configured path-specific header rules against an unnormalized, raw path that has only undergone URL percent-decoding. Unauthenticated remote attackers can exploit this discrepancy by injecting raw or percent-encoded dot-segments into HTTP requests, causing Rack to serve restricted static assets without their intended security headers.

Amit Schendel
Amit Schendel
5 views•10 min read
•about 4 hours ago•CVE-2026-118776
3.7

CVE-2026-118776: Improper PKCS#7 Padding Validation in node-forge CBC and ECB Cipher Modes

CVE-2026-118776 (also tracked as GHSA-2vgq-wchc-25w7) is an improper input and integrity check validation vulnerability affecting the node-forge cryptographic library prior to version 1.4.1. In vulnerable versions, the PKCS#7 unpadding implementations for Cipher Block Chaining (CBC) and Electronic Codebook (ECB) modes only examined the final byte of the decrypted buffer and permitted padding counts up to four times the cipher block size. This defect allowed malformed ciphertexts containing zero-length padding, oversized padding values, or mismatched preceding padding bytes to pass validation, resulting in silent plaintext truncation of up to four blocks and integrity verification bypass in applications relying on unpadding success.

Amit Schendel
Amit Schendel
2 views•10 min read
•about 5 hours ago•CVE-2026-118774
3.7

CVE-2026-118774: Improper Verification of Cryptographic Signature in node-forge X.509 Certificate Validation

CVE-2026-118774 (GHSA-9wg5-mj3p-v49q) is an improper cryptographic signature verification vulnerability (CWE-347) in node-forge versions prior to 1.4.1. When verifying an X.509 subordinate certificate via certificate.verify(child) or verifyCertificateChain(), the library erroneously selects the RSA padding scheme (e.g., PKCS#1 v1.5 vs. RSASSA-PSS) and RSASSA-PSS parameters from the issuer certificate rather than from the child certificate being verified. This enables an attacker to relabel a PKCS#1 v1.5-signed certificate as an RSASSA-PSS certificate without invalidating the signature check, bypassing downstream security policies that mandate RSASSA-PSS.

Amit Schendel
Amit Schendel
3 views•12 min read
•about 6 hours ago•CVE-2026-118670
8.8

CVE-2026-118670: DOM-Based Cross-Site Scripting in OHIF Viewers dicom-pdf Extension

CVE-2026-118670 (GHSA-xwgp-w3pj-q5h3) is a high-severity DOM-based Cross-Site Scripting (XSS) vulnerability in OHIF Viewers affecting the dicom-pdf extension module. When processing DICOM Encapsulated Document objects, the viewer extracts the untrusted MIMETypeOfEncapsulatedDocument attribute (tag 0042,0012) and uses it directly to instantiate an in-memory Blob. This Blob is then converted to a same-origin Object URL and embedded into the document via an HTML <object> element. Because browsers prioritize the resource's underlying MIME type over the <object> element's type attribute, an attacker who supplies a crafted DICOM file with a MIME type such as text/html can execute arbitrary JavaScript within the application's origin, compromising session tokens, local storage, and Patient Health Information (PHI).

Alon Barad
Alon Barad
3 views•9 min read