Security finding (HIGH, Cat 1 — Unbounded Allocation / DoS)
xrspatial.zonal._stats_numpy allocates an N-stat result array sized by user-controlled dimensions without a memory check:
https://github.com/makepath/xarray-spatial-contrib/blob/main/xrspatial/zonal.py#L505
else:
result = np.full((len(stats_funcs), values.size), np.nan)
This branch runs whenever the public stats(..., return_type='xarray.DataArray') is called. Memory required is n_stats * H * W * 8 bytes (float64), with n_stats user-controlled via the stats_funcs dict. For the documented default 8 stats on a 20000x20000 input the working buffer is ~25.6 GB; with a larger custom stats_funcs dict and/or input it scales linearly with both.
This mirrors the unbounded-allocation pattern already fixed in cost_distance (#1262), mahalanobis (#1288), kde (#1287), multispectral (#1291), sieve (#1296), resample (#1295), and several other modules.
Reproducer
import numpy as np
import xarray as xr
from xrspatial.zonal import stats
# Allocates n_stats * H * W * 8 bytes with no guard
zones = xr.DataArray(np.zeros((20000, 20000), dtype=np.int32))
values = xr.DataArray(np.zeros((20000, 20000), dtype=np.float32))
out = stats(zones=zones, values=values, return_type='xarray.DataArray')
# Working buffer >= 25 GB on top of the input rasters
Proposed fix
Add an _available_memory_bytes() / _check_memory(n_stats, h, w) helper to zonal.py and call it from _stats_numpy before the np.full allocation when return_type == 'xarray.DataArray'. Mirrors the established pattern (raise MemoryError with a clear message when n_stats * H * W * 8 exceeds 50% of available RAM). Dask / cupy paths return a DataFrame and are not affected; the cupy path already raises TypeError for 3D input.
Found by deep-sweep-security-zonal-2026-05-27 (carry-over MEDIUM from 2026-04-22 audit, re-classified HIGH given the same severity bar used for the related per-module unbounded-allocation fixes).
Security finding (HIGH, Cat 1 — Unbounded Allocation / DoS)
xrspatial.zonal._stats_numpyallocates an N-stat result array sized by user-controlled dimensions without a memory check:https://github.com/makepath/xarray-spatial-contrib/blob/main/xrspatial/zonal.py#L505
This branch runs whenever the public
stats(..., return_type='xarray.DataArray')is called. Memory required isn_stats * H * W * 8bytes (float64), withn_statsuser-controlled via thestats_funcsdict. For the documented default 8 stats on a 20000x20000 input the working buffer is ~25.6 GB; with a larger customstats_funcsdict and/or input it scales linearly with both.This mirrors the unbounded-allocation pattern already fixed in cost_distance (#1262), mahalanobis (#1288), kde (#1287), multispectral (#1291), sieve (#1296), resample (#1295), and several other modules.
Reproducer
Proposed fix
Add an
_available_memory_bytes()/_check_memory(n_stats, h, w)helper tozonal.pyand call it from_stats_numpybefore thenp.fullallocation whenreturn_type == 'xarray.DataArray'. Mirrors the established pattern (raiseMemoryErrorwith a clear message whenn_stats * H * W * 8exceeds 50% of available RAM). Dask / cupy paths return a DataFrame and are not affected; the cupy path already raisesTypeErrorfor 3D input.Found by deep-sweep-security-zonal-2026-05-27 (carry-over MEDIUM from 2026-04-22 audit, re-classified HIGH given the same severity bar used for the related per-module unbounded-allocation fixes).