GHSA-PGWH-4JJ4-QM8V

Vulnerability from github – Published: 2026-07-30 14:48 – Updated: 2026-07-30 14:48
VLAI
Summary
Flyto2 Core: Multiple HTTP-family modules fetch client-controlled URLs without the SSRF guard their siblings apply (SSRF to internal/metadata)
Details

Summary

Numerous HTTP-emitting modules (core.api.http_get, core.api.http_post, graphql.query/graphql.mutation, monitor.http_check, communication.slack_send, notification.{discord,slack,teams}.send_message, ai.vision_analyze [anthropic path], verify.visual_diff, browser.proxy_rotate, and the agent/llm inline base_url branch) perform outbound requests to a fully client-controlled URL without calling the project's own SSRF guard (validate_url_with_env_config) that their sibling modules apply. An authenticated workflow-author can point the URL at the cloud metadata IP (169.254.169.254), a loopback/RFC1918 host, or any internal host and read the response, yielding cloud-metadata credential theft and internal service read/write.

Root Cause

The SSRF guard is per-module (there is NO global egress interception). Each module must call validate_url_with_env_config before issuing a request. The listed modules never call it — they only carry an ssrf_protected metadata tag string which enforces nothing. Exemplar: src/core/modules/third_party/developer/http/requests.py — grepping for validate_url|ssrf|is_private in requests.py returns 0 guard calls; session.get(url) fires at :85 (HTTPGetModule) and session.post at :188 (HTTPPostModule). SECURITY.md incorrectly lists api.http_get as SSRF-protected.

Impact

Readable SSRF: full {status_code, headers, body} returned to the caller (requests.py:96-108). Enables theft of cloud IAM credentials from the metadata endpoint and read/write access to internal-only APIs. Scope Changed (S:C) — the request crosses into cloud-metadata / internal-network authority the workflow layer does not otherwise have.

Proof of Concept

Verified live this session: core.api.http_get with url pointed at a loopback internal server returned the internal body INTERNAL-SECRET-IAM-CREDENTIALS, while the guarded sibling http.get returned NETWORK_ERROR: Hostname blocked: 127.0.0.1 on the same input — proving the branch-asymmetry is real (not a port artifact).

POST /mcp {"method":"tools/call","params":{"name":"execute_module",
  "arguments":{"module_id":"core.api.http_get",
  "params":{"url":"http://<cloud-metadata-ip>/latest/meta-data/iam/security-credentials/"}}}}

Attack Chain

  1. Entry: authenticated MCP client → POST /mcp execute_module core.api.http_get, url set to the cloud metadata endpoint. Guard: require_auth (mcp.py:71). Bypass proof: passes with a valid workflow-author bearer token (PR:L).
  2. Check: capability denylist / enforce_module_policy (base.py:240). Bypass proof: core.api.* not in _DEFAULT_DENYLIST (module_policy.py:45-67) → is_allowed=True (runtime-registration verified: core.api.http_get -> HTTPGetModule).
  3. Check: SSRF validation. Bypass proof: requests.py has ZERO validate_url/ssrf calls — only the ssrf_protected tag string at :23/:116. session.get(url) fires at :85.
  4. Sink: aiohttp GET/POST to the cloud metadata IP.
  5. Impact: full response body returned → IAM credential theft, internal read/write.

Bypass Evidence

Grepping validate_url|ssrf|is_private in requests.py → 0 guard calls (2 hits are both the inert tag string). Live PoC returned internal body directly; guarded sibling blocked the same input. Direct IP works — no IPv6 transition trick needed (AC:L), unlike the seed CVE-2026-55787.

Affected Versions

<= 2.26.6 — code present on latest release tag v2.26.6 (requests.py:19,112).

Suggested Fix

Call validate_url_with_env_config(url) in each listed module before issuing the outbound request, matching the guarded siblings (e.g. ai.model:157, http.get). Best: route all outbound HTTP through a single guarded client wrapper so new modules inherit the guard.

Credit

Vulnerability discovered by zx (Jace).

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.26.6"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "flyto-core"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.26.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-67428"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-918"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-30T14:48:05Z",
    "nvd_published_at": "2026-07-29T19:16:52Z",
    "severity": "HIGH"
  },
  "details": "## Summary\nNumerous HTTP-emitting modules (`core.api.http_get`, `core.api.http_post`, `graphql.query`/`graphql.mutation`, `monitor.http_check`, `communication.slack_send`, `notification.{discord,slack,teams}.send_message`, `ai.vision_analyze` [anthropic path], `verify.visual_diff`, `browser.proxy_rotate`, and the `agent`/`llm` inline base_url branch) perform outbound requests to a fully client-controlled URL **without calling the project\u0027s own SSRF guard** (`validate_url_with_env_config`) that their sibling modules apply. An authenticated workflow-author can point the URL at the cloud metadata IP (169.254.169.254), a loopback/RFC1918 host, or any internal host and read the response, yielding cloud-metadata credential theft and internal service read/write.\n\n## Root Cause\nThe SSRF guard is per-module (there is NO global egress interception). Each module must call `validate_url_with_env_config` before issuing a request. The listed modules never call it \u2014 they only carry an `ssrf_protected` **metadata tag string** which enforces nothing. Exemplar: `src/core/modules/third_party/developer/http/requests.py` \u2014 grepping for `validate_url|ssrf|is_private` in requests.py returns 0 guard calls; `session.get(url)` fires at `:85` (HTTPGetModule) and `session.post` at `:188` (HTTPPostModule). SECURITY.md incorrectly lists `api.http_get` as SSRF-protected.\n\n## Impact\nReadable SSRF: full `{status_code, headers, body}` returned to the caller (requests.py:96-108). Enables theft of cloud IAM credentials from the metadata endpoint and read/write access to internal-only APIs. Scope Changed (S:C) \u2014 the request crosses into cloud-metadata / internal-network authority the workflow layer does not otherwise have.\n\n## Proof of Concept\nVerified live this session: `core.api.http_get` with `url` pointed at a loopback internal server returned the internal body `INTERNAL-SECRET-IAM-CREDENTIALS`, while the guarded sibling `http.get` returned `NETWORK_ERROR: Hostname blocked: 127.0.0.1` on the same input \u2014 proving the branch-asymmetry is real (not a port artifact).\n```\nPOST /mcp {\"method\":\"tools/call\",\"params\":{\"name\":\"execute_module\",\n  \"arguments\":{\"module_id\":\"core.api.http_get\",\n  \"params\":{\"url\":\"http://\u003ccloud-metadata-ip\u003e/latest/meta-data/iam/security-credentials/\"}}}}\n```\n\n## Attack Chain\n1. Entry: authenticated MCP client \u2192 `POST /mcp` execute_module `core.api.http_get`, url set to the cloud metadata endpoint. Guard: `require_auth` (mcp.py:71). Bypass proof: passes with a valid workflow-author bearer token (PR:L).\n2. Check: capability denylist / `enforce_module_policy` (base.py:240). Bypass proof: `core.api.*` not in `_DEFAULT_DENYLIST` (module_policy.py:45-67) \u2192 `is_allowed=True` (runtime-registration verified: `core.api.http_get -\u003e HTTPGetModule`).\n3. Check: SSRF validation. Bypass proof: `requests.py` has ZERO `validate_url`/ssrf calls \u2014 only the `ssrf_protected` tag string at :23/:116. `session.get(url)` fires at :85.\n4. Sink: aiohttp GET/POST to the cloud metadata IP.\n5. Impact: full response body returned \u2192 IAM credential theft, internal read/write.\n\n## Bypass Evidence\nGrepping `validate_url|ssrf|is_private` in requests.py \u2192 0 guard calls (2 hits are both the inert tag string). Live PoC returned internal body directly; guarded sibling blocked the same input. Direct IP works \u2014 no IPv6 transition trick needed (AC:L), unlike the seed CVE-2026-55787.\n\n## Affected Versions\n`\u003c= 2.26.6` \u2014 code present on latest release tag v2.26.6 (`requests.py:19,112`).\n\n## Suggested Fix\nCall `validate_url_with_env_config(url)` in each listed module before issuing the outbound request, matching the guarded siblings (e.g. `ai.model:157`, `http.get`). Best: route all outbound HTTP through a single guarded client wrapper so new modules inherit the guard.\n\n## Credit\n\nVulnerability discovered by zx (Jace).",
  "id": "GHSA-pgwh-4jj4-qm8v",
  "modified": "2026-07-30T14:48:05Z",
  "published": "2026-07-30T14:48:05Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/flytohub/flyto-core/security/advisories/GHSA-pgwh-4jj4-qm8v"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-67428"
    },
    {
      "type": "WEB",
      "url": "https://github.com/flytohub/flyto-core/commit/0a0a528520ec18f5a21f1ddf858a71cc1edfb6e9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/flytohub/flyto-core"
    },
    {
      "type": "WEB",
      "url": "https://github.com/flytohub/flyto-core/releases/tag/v2.26.7"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Flyto2 Core: Multiple HTTP-family modules fetch client-controlled URLs without the SSRF guard their siblings apply (SSRF to internal/metadata)"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…