Feb 19, 2026·6 min read·84 visits
Hermes versions 0.8.1 to <0.9.1 log command-line arguments in plaintext, including secrets passed via the '-O' flag. Attackers with access to log files (e.g., CI/CD artifacts) can harvest API tokens. Fixed in v0.9.1.
A classic case of 'logging too much,' CVE-2026-22798 reveals how the Hermes workflow automation tool accidentally documented its own secrets. By dumping the full command-line argument namespace into debug logs, the application exposed sensitive API tokens (like Invenio RDM authentication credentials) in plaintext. This vulnerability turns standard CI/CD build artifacts into a goldmine for attackers seeking lateral movement.
In the world of DevOps and scientific publishing, automation is king. We build tools to move data from Point A to Point B without human intervention. Enter hermes, a workflow tool designed to automate software publication with rich metadata. It connects to platforms like Invenio RDM, handling the heavy lifting of metadata curation and deposition.
But automation tools need keys to the kingdom. They need API tokens, authentication secrets, and specialized configurations to talk to these remote repositories. In hermes, these secrets are often passed via the command line options.
Here lies the problem: Developers love logs. We love them. We want to know exactly what our code is doing at every split second. But there is a fine line between 'helpful debugging information' and 'accidentally writing your PIN code on a billboard.' CVE-2026-22798 is a textbook example of crossing that line. It’s not a buffer overflow or a complex heap grooming exploit; it’s the application simply telling the log file secrets it promised to keep.
The root cause of this vulnerability is a fundamental misunderstanding—or perhaps just a moment of carelessness—regarding how Python's argparse library handles object representation.
In src/hermes/commands/cli.py, the application accepts command-line arguments to control its behavior. One of these arguments is -O (or --options), used to pass dynamic configuration values, including sensitive data like invenio_rdm.auth_token.
When the application starts, it parses these arguments into a Namespace object. The developer, likely intending to make debugging easier, decided to log this object:
# The culprit in src/hermes/commands/cli.py
log.debug("Running hermes with the following command line arguments: %s", args)Here is the catch: argparse.Namespace objects have a default __repr__ method that returns a string containing all attributes and their values. It does not discriminate. It does not redact. If you pass a token, that token becomes part of the string. The application then dutifully writes that string to hermes.log.
This is a CWE-532 (Insertion of Sensitive Information into Log File) at its purest. It’s functionally equivalent to print(password).
Let's look at the fix. The remediation didn't require re-architecting the authentication flow; it just required a muzzle for the logger.
The maintainers introduced a sanitization utility in src/hermes/utils.py. This function takes the arguments, creates a copy (to avoid modifying the actual runtime variables), and explicitly overwrites the sensitive options list with a placeholder string.
Here is the logic introduced in commit 90cb86acd026e7841f2539ae7a1b284a7f263514:
def mask_options_values(args: argparse.Namespace) -> argparse.Namespace:
import copy
masked_args = copy.copy(args)
# Check if we have options to mask
if hasattr(masked_args, "options") and masked_args.options:
# Overwrite the value in the tuple (key, value)
masked_args.options = [
(key, "***REDACTED***") for key, value in masked_args.options
]
return masked_argsThen, in the CLI entry point (src/hermes/commands/cli.py), they wrapped the logging call:
Vulnerable:
log.debug("Running hermes with the following command line arguments: %s", args)Patched:
log.debug("Running hermes with the following command line arguments: %s", mask_options_values(args))This ensures that while the application still functions with the real secrets, the persistent log record only sees ***REDACTED***. It’s a simple, effective patch, though one that highlights how manual data sanitization is often an afterthought.
How does a hacker exploit this? They likely don't need a shell on the production server immediately. They just need access to the artifacts.
hermes is a workflow tool, which means it likely runs inside CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins). These environments typically archive logs to help developers troubleshoot failed builds.
The Attack Chain:
hermes for publication. They gain read access to the build logs (e.g., as a junior developer, or via a misconfigured public Jenkins instance).hermes.log or views the console output of a previous run.auth_token or just scanning the Running hermes... lines reveals the plaintext credentials.$ grep -r "invenio_rdm.auth_token" ./build_logs/
hermes.log: DEBUG: Running hermes... Namespace(..., options=[('invenio_rdm.auth_token', 'eyJhbGciOiJIUz...'), ...])eyJ... token and authenticates directly against the Invenio RDM instance, bypassing the CI/CD controls entirely to modify, delete, or poison research data.> [!NOTE] > While the CVSS score is 'Medium' (5.9) due to the requirement of local/file access, in a modern DevSecOps environment, "local file access" to logs is often granted to hundreds of developers. The blast radius is wider than the vector suggests.
There is a strange discrepancy in the official reporting for this CVE. The NVD and GitHub Advisory vectors list Confidentiality impact as None (C:N) but Integrity impact as High (I:H).
Technically, this is absurd. The vulnerability is literally an information disclosure (Confidentiality loss). However, if we play devil's advocate, the scorer might be focusing on the outcome of the stolen token: using it to forge or modify data on the target repository (Integrity loss).
Regardless of how the bureaucracy scores it, the impact is clear:
hermes is used to publish software packages, a stolen token could allow an attacker to upload malicious versions of libraries, affecting downstream users.If you are using hermes anywhere in your stack, patching the binary is only step one.
1. Upgrade: Move to version 0.9.1 immediately. This stops the bleeding.
2. Rotate Credentials: This is the critical step most teams forget. Any token that has ever been passed to hermes in a vulnerable version must be considered compromised. Revoke the old tokens and issue new ones.
3. Purge Artifacts: Go back through your CI/CD history and delete old log files. If you have a centralized logging server (Splunk, ELK, Datadog), you need to scrub those indexes too. A patched application doesn't fix a log file from three months ago that's still sitting in an S3 bucket.
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:C/C:N/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
hermes softwarepub | >= 0.8.1, < 0.9.1 | 0.9.1 |
| Attribute | Detail |
|---|---|
| CWE | CWE-532 (Insertion of Sensitive Information into Log File) |
| CVSS v3.1 | 5.9 (Medium) |
| Attack Vector | Local (File Read) |
| Integrity Impact | High (via Stolen Credentials) |
| Confidentiality Impact | None (Official Vector) / High (Real World) |
| Exploit Status | No Known Public Exploit |
| Patch Commit | 90cb86acd026e7841f2539ae7a1b284a7f263514 |
The application writes sensitive information to a log file, where it may be disclosed to unauthorized parties.
CVE-2026-115895 is a high-severity code injection vulnerability (CWE-94) in the happy-dom npm package prior to version 21.0.4. The vulnerability exists within the ECMAScriptModuleCompiler component, which transforms ECMAScript module syntax into executable Node.js functions when JavaScript evaluation is enabled. Due to an incomplete fix for CVE-2026-33943, the compiler failed to validate local named import aliases against legal JavaScript identifier rules. Attackers supplying crafted module imports—specifically utilizing destructuring default initializers alongside JSON module attributes—can inject arbitrary JavaScript expressions directly into the host Node.js runtime context.
An improper input validation and authorization bypass vulnerability exists in the LangGraph storage layer affecting langgraph, langgraph-checkpoint-postgres, and langgraph-checkpoint-sqlite. Because relational store implementations serialize namespace tuples into dot-delimited strings without validating labels on read, search, delete, and batch operations, an attacker who controls a namespace label can inject periods (.) to traverse namespace boundaries and access, modify, or delete data belonging to other namespaces.
CVE-2026-115886 (GHSA-2wqp-q3c6-ccj5) is a Server-Side Request Forgery (SSRF) and Unsafe Reflection vulnerability in MLflow versions 3.12.0rc0 prior to 3.17.0. Unauthenticated REST API endpoints accept serialized third-party scorer JSON payloads that are dynamically deserialized and reflectively instantiated without sanitizing constructor keyword arguments. When optional dependencies such as trulens and trulens-providers-litellm are installed, attackers can inject egress-steering arguments into the scorer payload, forcing the MLflow server to dispatch outbound HTTP requests to arbitrary internal destinations or cloud metadata services.
CVE-2026-113872 (GHSA-q7qp-rjg7-q26m) is a high-severity denial-of-service vulnerability affecting the multer multipart/form-data upload middleware for Node.js and Express in versions 2.0.0 through 2.4.0. When an endpoint is configured to accept multiple files using an asynchronous fileFilter callback, a crafted, truncated multipart HTTP request can trigger an unhandled error event on the underlying busboy stream parser after request teardown has already completed. Because all error listeners are stripped during teardown, Node.js treats the resulting error event as an uncaught exception and immediately terminates the server process.
CVE-2026-115897 (also tracked as GHSA-m5mm-p8h7-m29v and aliased as CVE-2026-115754) is a Cross-Site Scripting (XSS) vulnerability in the Yahoo serialize-javascript npm package prior to version 7.1.3. The flaw exists in the regular expression used by escapeFunctionBody() to neutralize HTML script closing tags within serialized JavaScript functions. Because function bodies are sanitized in isolation before structural formatting is applied, a function whose string representation terminates directly with the sequence '</script' bypasses the positive lookahead check. When combined with serialization formatting options that introduce whitespace delimiters and a subsequent property that supplies a closing bracket, an attacker can prematurely terminate an enclosing HTML <script> block and inject arbitrary HTML or JavaScript into the rendered document.
CVE-2026-109841 is a Subresource Integrity (SRI) verification bypass vulnerability in undici, the official HTTP/1.1 and HTTP/2 client library for Node.js that powers the global fetch() implementation. The flaw stems from improper handling of case sensitivity (CWE-178) when validating cryptographic hash algorithm tokens within SRI metadata. Because undici compared incoming algorithm identifiers against a strictly lowercase set without prior ASCII-lowercase normalization, valid uppercase or mixed-case tokens such as SHA256- or Sha384- were treated as unrecognized algorithms and discarded. Under SRI forward-compatibility rules, discarding all provided tokens results in an empty integrity list, causing undici to fail open and accept tampered HTTP response bodies without cryptographic verification.