Someone left the master key under the doormat, and the door leads straight to the server room
CVE-2026-49869 is an authentication bypass chained to OS command injection in Kestra, an open-source event-driven orchestration platform used for data pipelines, ETL, and scheduled workflows. The AuthenticationFilter in Kestra OSS uses request.getPath().endsWith("/configs") to whitelist the public configuration endpoint from Basic Auth. Because this is a suffix match rather than an exact path comparison, any API path whose last segment is configs — e.g. /api/v1/main/flows/tutorial/configs — bypasses authentication entirely. Once past auth, an attacker uses the standard Kestra API to PUT a new flow containing a shell or Python task, POST an execution, and retrieve command output from logs. Kestra ships with plugin-script-shell and plugin-script-python enabled by default, so the chain lands unauthenticated RCE as root inside the Kestra worker container with zero clicks. Affected versions: < 1.0.45 and ≥ 1.1.0, < 1.3.21. Fixed in 1.0.45 and 1.3.21.
The vendor score of CVSS 10.0 is honest and, in this case, nearly correct. The vector AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H accurately reflects the zero-auth, zero-interaction, network-reachable nature of the bug and the changed-scope impact (container → host/network pivot). The only marginal adjustment is that initial RCE lands inside a worker *container*, not directly on the host — but in practice, Kestra workers routinely mount host volumes, hold cloud credentials, and connect to production databases, so the effective blast radius is fleet-scale. Active exploitation is confirmed (KEV added 2026-09-02), a full weaponized PoC with --rce and --ssrf flags is public on GitHub, and CISA's remediation deadline is September 5, 2026 — two days from now. This is as close to a justified 10.0 as you'll see.
5 steps from start to impact.
Discover Kestra instance
/api/v1/configs endpoint which is intentionally unauthenticated and returns version metadata. The PoC's liveness check (GET /api/v1/configs) confirms the target and extracts the running version.- Network reachability to Kestra API (port 8080/8081 or reverse proxy)
- Many Kestra deployments sit behind a VPN or internal network, not directly internet-exposed
- Reverse proxies may strip or rewrite path segments
/configs suffix pattern on non-root pathsBypass authentication via suffix match
/configs appended as the final path segment, e.g. GET /api/v1/main/flows/tutorial/configs. The AuthenticationFilter sees the path ends in /configs and skips Basic Auth validation. The server returns 200 instead of 401, confirming full unauthenticated API access. The PoC tests six different endpoint families to find a working bypass.- Kestra version < 1.0.45 or ≥ 1.1.0 and < 1.3.21
- Basic Auth enabled (default for OSS)
- Patched versions (≥ 1.0.45 or ≥ 1.3.21) use exact path matching
- Enterprise edition may use different auth mechanisms (SSO/OIDC), though the underlying filter flaw may still apply
/api/v1/*/configs where the second-to-last segment is not empty; Kestra access logs showing 200 responses to anomalous /configs suffixed pathsDeploy malicious workflow
PUT /api/v1/main/flows/configs (or equivalent bypassed path) with a JSON/YAML flow definition containing a io.kestra.plugin.scripts.shell.Commands task. The task body contains arbitrary OS commands — reverse shell, credential harvester, or cryptominer. Kestra accepts and registers the flow without authentication.- Script execution plugins enabled (default: plugin-script-shell, plugin-script-python)
- If an operator has explicitly removed all script plugins, this step fails — but this is non-default and extremely rare
Trigger execution and achieve RCE
POST /api/v1/main/executions/configs (bypassed path) referencing the newly created flow. Kestra's worker picks up the execution and runs the shell commands as root inside the worker container. The attacker retrieves output via GET on the execution logs endpoint. The PoC automates this entire chain with a single --rce flag.- At least one active Kestra worker
- Container isolation may limit initial blast radius to the worker container
- Network policies (Kubernetes NetworkPolicy, Docker network segmentation) may restrict lateral movement
Pivot to host, cloud, and data tier
--ssrf mode targeting 169.254.169.254). From here, the attacker pivots to cloud infrastructure, production data stores, or upstream/downstream pipeline dependencies.- Worker container has cloud credentials or host volume mounts (very common in production)
- IMDS v2 (AWS) requires token-based access, blocking simple SSRF metadata harvesting
- Workload identity federation with short-lived tokens limits credential lifetime
The supporting signals.
| In-the-Wild Exploitation | Confirmed. CISA added CVE-2026-49869 to the KEV catalog on 2026-09-02. Active exploitation reported in the wild. |
|---|---|
| KEV Status | Listed. Added 2026-09-02, remediation due 2026-09-05. Binding for FCEB agencies under BOD 22-01. |
| Proof-of-Concept | Public and weaponized. Ap0dexMe0/CVE-2026-49869 — Python PoC with --rce, --ssrf, and --destructive flags. Fully automated exploit chain. |
| EPSS Score | 0.00991 (~1.0%). Percentile is low relative to the actual risk — EPSS lags KEV listings for newer CVEs. |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H — 10.0 CRITICAL. Network-reachable, no auth, no interaction, changed scope, full CIA impact. |
| Affected Versions | Kestra OSS < 1.0.45 and ≥ 1.1.0, < 1.3.21. Version 1.4.0+ unaffected. |
| Fixed Versions | 1.0.45 (LTS branch) and 1.3.21 (mainline). Upgrade path is straightforward — no schema migrations reported. |
| Scanning/Exposure | No published Shodan/Censys census for Kestra specifically. Default ports 8080/8081 are generic. Internet exposure likely limited but growing as Kestra adoption increases in data engineering teams. |
| Disclosure Date | 2026-06-26. Patch available same day. ~69 days elapsed before KEV listing. |
| Researcher | Credited to Ap0dexMe0 (GitHub). Additional Kestra vulns (CVE-2026-34612, CVE-2026-53576) reported in the same timeframe suggest coordinated audit. |
noisgate verdict.
The single most decisive factor is the role multiplier — Kestra is canonically a CI/CD and workflow orchestration platform, placing it squarely in the high-value-role catalog where compromise yields supply-chain pivots, cloud credential harvesting, and production data access at fleet scale. Combined with unauthenticated remote exploitation, a public weaponized PoC, and confirmed active exploitation (KEV-listed), no downgrade is defensible.
Why this verdict
- Unauthenticated RCE with zero friction: The entire chain — auth bypass → flow creation → code execution — requires no credentials, no user interaction, and works against default configurations. The PoC automates it in a single command.
- Public weaponized exploit: The GitHub PoC (Ap0dexMe0/CVE-2026-49869) includes
--rce,--ssrf, and--destructivemodes. This is not a theoretical advisory — it's a point-and-shoot tool. - KEV-listed with active exploitation: CISA added this on 2026-09-02 with a 3-day remediation deadline (2026-09-05), confirming real-world attacks. This alone prevents any downgrade.
- Role multiplier: Kestra is a CI/CD and workflow orchestration platform by definition — ≥90% of installs occupy this high-value role. Successful exploitation yields access to pipeline secrets, cloud credentials, production database connections, and downstream supply-chain artifacts. Blast radius: host → cloud tenant → supply chain. The floor is CRITICAL.
- Default-dangerous configuration: Script execution plugins (
plugin-script-shell,plugin-script-python) ship enabled by default. Workers run as root. No opt-in required for the attacker to achieve code execution.
Why not higher?
The score is already 9.8 CRITICAL, which is the practical maximum for a single-product vulnerability. The 0.2-point reduction from the vendor's 10.0 reflects that initial code execution lands inside a worker container (not directly on the host kernel), and Kestra's total installed base is smaller than tier-1 CI/CD platforms like Jenkins or GitLab, limiting the global exposure population.
Why not lower?
Every downgrade factor fails here. Auth bypass eliminates the 'requires authentication' friction. Default plugin configuration eliminates 'requires non-default setup'. KEV listing with active exploitation eliminates 'theoretical only'. The CI/CD role multiplier sets a CRITICAL floor that cannot be broken without evidence that <1% of Kestra instances are in production orchestration roles — and by definition, that's what Kestra *is*.
What to do — in priority order.
- Block external access to Kestra API immediately — Place Kestra behind a VPN, zero-trust proxy, or firewall rule that restricts API access (ports 8080/8081) to authorized operator IPs only. This is your noisgate mitigation SLA: deploy within 3 days (but given KEV status, do it today). This neutralizes the unauthenticated remote vector entirely.
- Deploy WAF rule to block suffix-matched /configs paths — If you cannot patch immediately, add a WAF or reverse proxy rule that rejects any request to
/api/v1/*/configswhere the path has more than 3 segments. This surgically blocks the bypass without disrupting the legitimate/api/v1/configsendpoint. - Remove or disable script execution plugins — If Kestra must remain exposed temporarily, remove
plugin-script-shellandplugin-script-pythonfrom the plugins directory. This breaks the RCE chain even if auth is bypassed — the attacker can create flows but cannot execute code. Note: this will break legitimate pipelines that use shell/Python tasks. - Enforce container network policies — Apply Kubernetes NetworkPolicy or Docker network restrictions to Kestra worker containers, blocking egress to cloud metadata endpoints (169.254.169.254) and limiting lateral movement to only required data sources. This contains blast radius if the worker is compromised.
- Rotate all secrets accessible to Kestra workers — After patching, rotate cloud credentials, database passwords, API keys, and any secrets stored in Kestra's KV store or injected as environment variables. Assume compromise if you were running an affected version with any internet exposure.
- EDR on the host alone — RCE executes inside the worker container. Unless your EDR has container-aware sensors (Falco, Sysdig, Aqua), host-level EDR may not see the malicious process.
- Rate limiting / brute-force protection — This is not a brute-force attack. A single crafted request bypasses auth. Rate limiting is irrelevant.
- Upgrading to Kestra Enterprise auth (OIDC/SSO) without patching — The suffix-match flaw is in the core
AuthenticationFiltercode path. Enterprise auth may still route through the same filter. Only the patch (exact path match) fixes the root cause.
Crowdsourced verification payload.
Run this script on any host with curl and network access to the target Kestra instance. Usage: bash check_cve_2026_49869.sh https://kestra.internal:8080. No special privileges required — it performs read-only HTTP requests.
#!/usr/bin/env bash
# CVE-2026-49869 Kestra Auth Bypass Checker
# Usage: bash check_cve_2026_49869.sh <KESTRA_BASE_URL>
# Example: bash check_cve_2026_49869.sh https://kestra.internal:8080
# Requires: curl, jq (optional for version parsing)
# Exit codes: 0=VULNERABLE, 1=PATCHED, 2=UNKNOWN
set -euo pipefail
BASE_URL="${1:?Usage: $0 <KESTRA_BASE_URL>}"
BASE_URL="${BASE_URL%/}"
echo "[*] Checking CVE-2026-49869 against $BASE_URL"
# Step 1: Liveness check
HTTP_CODE=$(curl -sk -o /dev/null -w '%{http_code}' "$BASE_URL/api/v1/configs" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "000" ]; then
echo "[!] Cannot reach $BASE_URL — check connectivity"
echo "UNKNOWN"
exit 2
fi
echo "[*] Liveness: /api/v1/configs returned HTTP $HTTP_CODE"
# Step 2: Check version if possible
VERSION=$(curl -sk "$BASE_URL/api/v1/configs" 2>/dev/null | grep -oP '"version"\s*:\s*"\K[^"]+' || echo "unknown")
echo "[*] Detected Kestra version: $VERSION"
# Step 3: Confirm auth is enforced on normal endpoint
AUTH_CODE=$(curl -sk -o /dev/null -w '%{http_code}' "$BASE_URL/api/v1/main/flows" 2>/dev/null || echo "000")
echo "[*] Auth baseline: /api/v1/main/flows returned HTTP $AUTH_CODE"
if [ "$AUTH_CODE" = "200" ]; then
echo "[!] Warning: /api/v1/main/flows returned 200 without auth — auth may be disabled entirely"
echo "UNKNOWN"
exit 2
fi
# Step 4: Test bypass paths
BYPASS_PATHS=(
"/api/v1/main/flows/tutorial/configs"
"/api/v1/main/executions/tutorial/configs"
"/api/v1/main/namespaces/tutorial/kv/configs"
"/api/v1/main/dashboards/configs"
)
VULN=0
for path in "${BYPASS_PATHS[@]}"; do
CODE=$(curl -sk -o /dev/null -w '%{http_code}' "$BASE_URL$path" 2>/dev/null || echo "000")
if [ "$CODE" = "200" ]; then
echo "[!] BYPASS CONFIRMED: $path returned HTTP 200"
VULN=1
else
echo "[*] $path returned HTTP $CODE (blocked)"
fi
done
if [ "$VULN" -eq 1 ]; then
echo ""
echo "VULNERABLE"
echo "[!] This instance is affected by CVE-2026-49869. Patch to >= 1.0.45 or >= 1.3.21 immediately."
exit 0
else
echo ""
echo "PATCHED"
echo "[*] Auth bypass paths are blocked. Instance appears patched or mitigated."
exit 1
fiIf you remember one thing.
Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.