CVE-2026-54136: Windmill Remote Code Execution Vulnerability

MODERATE
Advisory Identifier: GHSA-2ppx-66jv-wpw5
CWE-863
Published
Advisory Summary

CVE-2026-54136 is a MODERATE remote code execution vulnerability (CWE-863: Incorrect Authorization) affecting Windmill libraries in the crates.io ecosystem. The issue involves resource-scoped api tokens can read script contents outside their allowed path via scripts/list_search. Remediation is available by upgrading affected packages to version 1.715.0.

CVE-2026-54136 at a Glance

CVE Identifier CVE-2026-54136
GHSA Identifier GHSA-2ppx-66jv-wpw5
Severity MODERATE
CVSS v3.1 Base Score N/A
CWE Classification CWE-863: Incorrect Authorization
Affected Product Windmill
Package Ecosystem crates.io
Published Date
Last Updated

How to Fix CVE-2026-54136

To remediate this vulnerability, upgrade all affected Windmill dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

windmill-api fixed: 1.715.0
Upgrade windmill-api to 1.715.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for windmill-api

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-54136, inspect your project dependencies for Windmill:

inspect dependencies for windmill-api

Analyze the terminal output to locate any version of Windmill in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.715.0), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-863: Incorrect Authorization, where the product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. In Windmill, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary A resource-scoped API token can read script contents outside its allowed path scope via `GET /api/w/{workspace}/scripts/list_search`. This appears to be a remaining variant of the scoped-token authorization class previously addressed for other endpoints. The route-level scope middleware validates the token domain/action, but does not enforce the resource/path segment of a scope. `scripts/list_search` then returns script `path` and `content` for scripts in the workspace without applying per-row path filtering against the token scopes. ### Affected endpoint `GET /api/w/{workspace}/scripts/list_search` ### Affected versions Confirmed in the current public repository code and believed to affect the latest published release at the time of review: `<= 1.714.1` Patched version: unknown. ### Details Windmill supports scoped API tokens with scopes in the format: `{domain}:{action}[:{resource}]` The parser supports resource-scoped values such as: `scripts:read:f/allowed/*` and the codebase contains helpers for resource matching, including wildcard matching. However, the route-level scope check used for requests with scoped API tokens only validates the route domain and action. It does not compare the token's resource/path restriction against the requested route or against the rows returned by list endpoints. For `scripts/list_search`, the handler returns `path` and `content` for scripts in the workspace: `SELECT path, content from script WHERE workspace_id = $1 AND archived = false LIMIT $2` There is no additional `check_scopes(...)` call in the handler and no per-row filtering based on the token's resource/path scope. As a result, a token intended to read only scripts under one path prefix may be able to read script contents from unrelated paths in the same workspace. ### Source-level reproduction 1. Create or use a workspace containing at least two scripts: - `f/allowed/script_a` - `f/private/script_b` 2. Create a scoped API token intended to read only the allowed path: `scripts:read:f/allowed/*` 3. Use that token to call: `GET /api/w/{workspace}/scripts/list_search` 4. Expected behavior: The response should include only scripts matching the token's resource scope, e.g. only scripts under: `f/allowed/*` 5. Actual behavior from source review: The route-level scope check accepts the request as `scripts:read`, and the handler returns script `path` and `content` for scripts in the workspace without filtering the rows by the token's resource scope. This can expose script source code from paths outside the token's intended scope. ### Impact A user or integration holding a path-restricted `scripts:read:{resource}` token may be able to read script contents from unrelated scripts in the same workspace. Depending on how scripts are used, this may disclose: - internal automation logic, - integration details, - business logic, - inline configuration, - accidentally hardcoded secrets or credentials. This does not require admin privileges. It requires possession of a valid scoped API token for the workspace. ### Related context This appears related to the broader class of issues where route-level token scope enforcement validates domain/action but not the resource/path portion of the scope. Similar scoped-token issues appear to have been fixed for other endpoints, such as resources/variables listing and job preview/run paths, but I did not find an equivalent fix for `scripts/list_search`. ### Suggested fix Apply resource/path scope enforcement to `scripts/list_search`. Possible approaches: 1. Add explicit handler-level authorization similar to per-resource endpoints. 2. Filter returned rows so that a scoped token only receives scripts whose `path` is included by at least one `scripts:read:{resource}` scope. 3. Add regression tests for: - `scripts:read:f/allowed/*` cannot see `f/private/script_b`, - broad `scripts:read` still sees all accessible scripts, - unscoped tokens preserve current behavior, - filter-tag-only tokens preserve current compatibility behavior. A more defensive long-term fix would be to make route-level scope enforcement aware of resource/path restrictions where the route contains a concrete resource path, while list endpoints should apply per-row filtering.

CVE-2026-54136 Affected Windmill Versions

1 component(s)
Fixed Versions:
1.715.0
Product / Package Ecosystem Affected Versions Fixed Version
windmill-api
crates.io
< 1.715.0
1.715.0

References

Frequently Asked Questions

What is CVE-2026-54136?
CVE-2026-54136 is a security vulnerability in Windmill involving Resource-scoped API tokens can read script contents outside their allowed path via scripts/list_search under CWE-863: Incorrect Authorization.
Which Windmill versions are affected?
Affected versions include releases prior to 1.715.0 across affected packages: windmill-api.
What version fixes CVE-2026-54136?
Upgrading to 1.715.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is windmill-api affected?
Yes, windmill-api is affected in releases prior to 1.715.0. Upgrade your dependency to 1.715.0 or later.
What CWE is associated with CVE-2026-54136?
CVE-2026-54136 is classified under CWE-863: Incorrect Authorization (The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check.).
How can I check crates.io dependencies for CVE-2026-54136?
Run the dependency tree command: 'inspect dependencies for windmill-api'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-2ppx-66jv-wpw5",
  "modified" : "2026-07-10T19:28:06Z",
  "published" : "2026-07-10T19:28:06Z",
  "aliases" : [ "CVE-2026-54136" ],
  "summary" : "Windmill: Resource-scoped API tokens can read script contents outside their allowed path via scripts/list_search",
  "details" : "### Summary\n\nA resource-scoped API token can read script contents outside its allowed path scope via `GET /api/w/{workspace}/scripts/list_search`.\n\nThis appears to be a remaining variant of the scoped-token authorization class previously addressed for other endpoints. The route-level scope middleware validates the token domain/action, but does not enforce the resource/path segment of a scope. `scripts/list_search` then returns script `path` and `content` for scripts in the workspace without applying per-row path filtering against the token scopes.\n\n### Affected endpoint\n\n`GET /api/w/{workspace}/scripts/list_search`\n\n### Affected versions\n\nConfirmed in the current public repository code and believed to affect the latest published release at the time of review:\n\n`<= 1.714.1`\n\nPatched version: unknown.\n\n### Details\n\nWindmill supports scoped API tokens with scopes in the format:\n\n`{domain}:{action}[:{resource}]`\n\nThe parser supports resource-scoped values such as:\n\n`scripts:read:f/allowed/*`\n\nand the codebase contains helpers for resource matching, including wildcard matching.\n\nHowever, the route-level scope check used for requests with scoped API tokens only validates the route domain and action. It does not compare the token's resource/path restriction against the requested route or against the rows returned by list endpoints.\n\nFor `scripts/list_search`, the handler returns `path` and `content` for scripts in the workspace:\n\n`SELECT path, content from script WHERE workspace_id = $1 AND archived = false LIMIT $2`\n\nThere is no additional `check_scopes(...)` call in the handler and no per-row filtering based on the token's resource/path scope.\n\nAs a result, a token intended to read only scripts under one path prefix may be able to read script contents from unrelated paths in the same workspace.\n\n### Source-level reproduction\n\n1. Create or use a workspace containing at least two scripts:\n\n- `f/allowed/script_a`\n- `f/private/script_b`\n\n2. Create a scoped API token intended to read only the allowed path:\n\n`scripts:read:f/allowed/*`\n\n3. Use that token to call:\n\n`GET /api/w/{workspace}/scripts/list_search`\n\n4. Expected behavior:\n\nThe response should include only scripts matching the token's resource scope, e.g. only scripts under:\n\n`f/allowed/*`\n\n5. Actual behavior from source review:\n\nThe route-level scope check accepts the request as `scripts:read`, and the handler returns script `path` and `content` for scripts in the workspace without filtering the rows by the token's resource scope.\n\nThis can expose script source code from paths outside the token's intended scope.\n\n### Impact\n\nA user or integration holding a path-restricted `scripts:read:{resource}` token may be able to read script contents from unrelated scripts in the same workspace.\n\nDepending on how scripts are used, this may disclose:\n\n- internal automation logic,\n- integration details,\n- business logic,\n- inline configuration,\n- accidentally hardcoded secrets or credentials.\n\nThis does not require admin privileges. It requires possession of a valid scoped API token for the workspace.\n\n### Related context\n\nThis appears related to the broader class of issues where route-level token scope enforcement validates domain/action but not the resource/path portion of the scope. Similar scoped-token issues appear to have been fixed for other endpoints, such as resources/variables listing and job preview/run paths, but I did not find an equivalent fix for `scripts/list_search`.\n\n### Suggested fix\n\nApply resource/path scope enforcement to `scripts/list_search`.\n\nPossible approaches:\n\n1. Add explicit handler-level authorization similar to per-resource endpoints.\n2. Filter returned rows so that a scoped token only receives scripts whose `path` is included by at least one `scripts:read:{resource}` scope.\n3. Add regression tests for:\n   - `scripts:read:f/allowed/*` cannot see `f/private/script_b`,\n   - broad `scripts:read` still sees all accessible scripts,\n   - unscoped tokens preserve current behavior,\n   - filter-tag-only tokens preserve current compatibility behavior.\n\nA more defensive long-term fix would be to make route-level scope enforcement aware of resource/path restrictions where the route contains a concrete resource path, while list endpoints should apply per-row filtering.",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "crates.io",
      "name" : "windmill-api"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.715.0"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 1.714.1"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/windmill-labs/windmill/security/advisories/GHSA-2ppx-66jv-wpw5"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/windmill-labs/windmill"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-863" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-10T19:28:06Z",
    "nvd_published_at" : null
  }
}