Pull Request Overview
- Opened on September 18, 2026
- Status Open
- Commit count 2 with first commit September 18, 2026
Total Delta
Open Days
Test Delta
How long has this pull request spent in each phase of its lifecycle?
| Fraction of total time | Business days | Phase |
|---|---|---|
|
|
0.0 days | Authoring 1 commit before pull request opened for review |
|
|
0.0 days | Awaiting first review |
Total time for pull request still awaiting merge: 0.1 business days
ES|QL|DS: hide storage location without privilege
A user who holds only read on a dataset (which authorises
indices:data/read/esql/resolve_datasets) was able to discover the dataset's
full storage location β S3 bucket, GCS bucket, Azure container, object key,
prefix β through two independent routes:
Failed-query error messages. Every storage layer (S3, GCS, Azure), every
resolver stage (ExternalSourceResolver,FormatNameResolver), and every
format reader (ParquetFormatReader) embedded the path directly into
exception messages. Those messages crossed the node boundary and reached the
user verbatim in thereason/caused_bychain.Profile plan strings on success. With
profile: true, every entry inprofile.planscontained the full storage URI viaExternalSourceExec's
node string.
The right that separates these two callers already exists:
indices:admin/esql/dataset/get (granted by read_dataset_metadata,
manage_dataset, manage, or all). This PR makes both paths consult it.
What changed
Error-message path
Strip path at every composing site. All
IOException/IllegalArgumentExceptionconstructions across S3, GCS, Azure storage
objects and providers,FormatNameResolver,ExternalSourceResolver,ParquetFormatReader,FileSourceFactory, andTableCatalognow name the
operation and condition without embedding the path.ExternalFailures.resolutionFailureMessagewas deleted; its call sites useExternalFailures.rootDetail(e)directly (path-free, wrapper-transparent).Carry location in a structured field.
ExternalFailures.LocatedException
wraps every resolution-path failure as a pair: an unlocated form (no path)
and a located form (path reinstated). The coordinator selects between them
based on privilege.One privilege check on the coordinator.
EsqlSessionchecksindices:admin/esql/dataset/getviaHasPrivilegesRequestafter the
caller's thread context is restored. The result (canSeeDatasetLocation) is
stored onConfigurationbehind a newTransportVersion
(esql_dataset_location_visible) so data nodes receive it too. Security-
disabled clusters short-circuit totrue.Reinstatement.
reinstateLocationIfAuthorizedinEsqlSessionresolves
everyLocatedExceptionto its located or unlocated form before the failure
reaches the user.
Profile/EXPLAIN plan path
-
NodeStringMappergains an abstractlocation(String)method.IDENTITY.location()returns verbatim; newREDACT_LOCATION.location()
returns"[redacted]". -
ExternalSourceExec.nodeString()andExternalRelation.nodeString()now
callmapper.location(sourcePath)instead ofmapper.opaque(sourcePath). -
ComputeServiceand all threeEXPLAINrendering call sites inEsqlSession
select the mapper fromconfiguration.canSeeDatasetLocation().
Closes: elastic/esql-planning#1981
Comments Threads Pending Resolution
Resolved Comment Threads
No resolved comments have been left on this PR.