Two places where a path read from table metadata is acted on without any check that it falls under the table's own location.
1. purge_table deletes whatever paths the manifests name
Catalog.purge_table (pyiceberg/catalog/__init__.py) walks every snapshot, collects manifests, manifest lists and previous metadata files, and calls delete_data_files(io, manifests_to_delete). The file_path entries inside those manifests are followed as given. Nothing constrains them to the table's location, so a manifest naming a path elsewhere in the warehouse results in a delete against that path, performed with the credentials of whoever ran the purge.
2. write.data.path and write.metadata.path are accepted verbatim
if path := table_properties.get(TableProperties.WRITE_DATA_PATH):
self.data_path = path.rstrip("/")
else:
self.data_path = f"{self.table_location.rstrip('/')}/data"
LocationProvider.__init__ (pyiceberg/table/locations.py) takes the configured value as-is. Subsequent writes for that table go wherever it points, again with the writing principal's credentials.
Note that redirecting the write location is the documented purpose of these two properties, so the gap is the absence of a containment check rather than the fact that the properties are honoured at all.
Issue investigation generated via claude, reviewed by Sung, Kevin, Fokko.
Two places where a path read from table metadata is acted on without any check that it falls under the table's own location.
1.
purge_tabledeletes whatever paths the manifests nameCatalog.purge_table(pyiceberg/catalog/__init__.py) walks every snapshot, collects manifests, manifest lists and previous metadata files, and callsdelete_data_files(io, manifests_to_delete). Thefile_pathentries inside those manifests are followed as given. Nothing constrains them to the table's location, so a manifest naming a path elsewhere in the warehouse results in a delete against that path, performed with the credentials of whoever ran the purge.2.
write.data.pathandwrite.metadata.pathare accepted verbatimLocationProvider.__init__(pyiceberg/table/locations.py) takes the configured value as-is. Subsequent writes for that table go wherever it points, again with the writing principal's credentials.Note that redirecting the write location is the documented purpose of these two properties, so the gap is the absence of a containment check rather than the fact that the properties are honoured at all.
Issue investigation generated via claude, reviewed by Sung, Kevin, Fokko.