|
| 1 | +# Security Sweep: Dispatch subagents to audit modules for security vulnerabilities |
| 2 | + |
| 3 | +Audit xrspatial modules for security issues specific to numeric/GPU raster |
| 4 | +libraries: unbounded allocations, integer overflow, NaN logic bombs, GPU |
| 5 | +kernel bounds, file path injection, and dtype confusion. Subagents fix |
| 6 | +CRITICAL/HIGH issues via /rockout. |
| 7 | + |
| 8 | +Optional arguments: $ARGUMENTS |
| 9 | +(e.g. `--top 3`, `--exclude slope,aspect`, `--only-io`, `--reset-state`) |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## Step 1 -- Gather module metadata via git and grep |
| 14 | + |
| 15 | +Enumerate candidate modules: |
| 16 | + |
| 17 | +**Single-file modules:** Every `.py` file directly under `xrspatial/`, excluding |
| 18 | +`__init__.py`, `_version.py`, `__main__.py`, `utils.py`, `accessor.py`, |
| 19 | +`preview.py`, `dataset_support.py`, `diagnostics.py`, `analytics.py`. |
| 20 | + |
| 21 | +**Subpackage modules:** `geotiff/`, `reproject/`, and `hydro/` directories under |
| 22 | +`xrspatial/`. Treat each as a single audit unit. List all `.py` files within |
| 23 | +each (excluding `__init__.py`). |
| 24 | + |
| 25 | +For every module, collect: |
| 26 | + |
| 27 | +| Field | How | |
| 28 | +|-------|-----| |
| 29 | +| **last_modified** | `git log -1 --format=%aI -- <path>` (for subpackages, most recent file) | |
| 30 | +| **total_commits** | `git log --oneline -- <path> \| wc -l` | |
| 31 | +| **loc** | `wc -l < <path>` (for subpackages, sum all files) | |
| 32 | +| **has_cuda_kernels** | grep file(s) for `@cuda.jit` | |
| 33 | +| **has_file_io** | grep file(s) for `open(`, `mkstemp`, `os.path`, `pathlib` | |
| 34 | +| **has_numba_jit** | grep file(s) for `@ngjit`, `@njit`, `@jit`, `numba.jit` | |
| 35 | +| **allocates_from_dims** | grep file(s) for `np.empty(height`, `np.zeros(height`, `np.empty(H`, `np.empty(h `, `cp.empty(`, and width variants | |
| 36 | +| **has_shared_memory** | grep file(s) for `cuda.shared.array` | |
| 37 | + |
| 38 | +Store results in memory -- do NOT write intermediate files. |
| 39 | + |
| 40 | +## Step 2 -- Load inspection state |
| 41 | + |
| 42 | +Read `.claude/security-sweep-state.json`. |
| 43 | + |
| 44 | +If it does not exist, treat every module as never-inspected. |
| 45 | + |
| 46 | +If `$ARGUMENTS` contains `--reset-state`, delete the file and treat |
| 47 | +everything as never-inspected. |
| 48 | + |
| 49 | +State file schema: |
| 50 | + |
| 51 | +```json |
| 52 | +{ |
| 53 | + "inspections": { |
| 54 | + "cost_distance": { |
| 55 | + "last_inspected": "2026-04-10T14:00:00Z", |
| 56 | + "issue": 1150, |
| 57 | + "severity_max": "HIGH", |
| 58 | + "categories_found": [1, 2] |
| 59 | + } |
| 60 | + } |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +## Step 3 -- Score each module |
| 65 | + |
| 66 | +``` |
| 67 | +days_since_inspected = (today - last_inspected).days # 9999 if never |
| 68 | +days_since_modified = (today - last_modified).days |
| 69 | +
|
| 70 | +score = (days_since_inspected * 3) |
| 71 | + + (has_file_io * 400) |
| 72 | + + (allocates_from_dims * 300) |
| 73 | + + (has_cuda_kernels * 250) |
| 74 | + + (has_shared_memory * 200) |
| 75 | + + (has_numba_jit * 100) |
| 76 | + + (loc * 0.05) |
| 77 | + - (days_since_modified * 0.2) |
| 78 | +``` |
| 79 | + |
| 80 | +Rationale: |
| 81 | +- File I/O is the only external-escape vector (400) |
| 82 | +- Unbounded allocation is a DoS vector across all backends (300) |
| 83 | +- CUDA bugs cause silent memory corruption (250) |
| 84 | +- Shared memory overflow is a CUDA sub-risk (200) |
| 85 | +- Numba JIT is ubiquitous -- lower weight avoids noise (100) |
| 86 | +- Larger files have more surface area (0.05 per line) |
| 87 | +- Recently modified code slightly deprioritized |
| 88 | + |
| 89 | +## Step 4 -- Apply filters from $ARGUMENTS |
| 90 | + |
| 91 | +- `--top N` -- only audit the top N modules (default: 3) |
| 92 | +- `--exclude mod1,mod2` -- remove named modules from the list |
| 93 | +- `--only-terrain` -- restrict to: slope, aspect, curvature, terrain, |
| 94 | + terrain_metrics, hillshade, sky_view_factor |
| 95 | +- `--only-focal` -- restrict to: focal, convolution, morphology, bilateral, |
| 96 | + edge_detection, glcm |
| 97 | +- `--only-hydro` -- restrict to: flood, cost_distance, geodesic, |
| 98 | + surface_distance, viewshed, erosion, diffusion, hydro (subpackage) |
| 99 | +- `--only-io` -- restrict to: geotiff, reproject, rasterize, polygonize |
| 100 | + |
| 101 | +## Step 5 -- Print the ranked table and launch subagents |
| 102 | + |
| 103 | +### 5a. Print the ranked table |
| 104 | + |
| 105 | +Print a markdown table showing ALL scored modules (not just selected ones), |
| 106 | +sorted by score descending: |
| 107 | + |
| 108 | +``` |
| 109 | +| Rank | Module | Score | Last Inspected | CUDA | FileIO | Alloc | Numba | LOC | |
| 110 | +|------|-----------------|--------|----------------|------|--------|-------|-------|------| |
| 111 | +| 1 | geotiff | 30600 | never | yes | yes | no | yes | 1400 | |
| 112 | +| 2 | hydro | 30300 | never | yes | no | yes | yes | 8200 | |
| 113 | +| ... | ... | ... | ... | ... | ... | ... | ... | ... | |
| 114 | +``` |
| 115 | + |
| 116 | +### 5b. Launch subagents for the top N modules |
| 117 | + |
| 118 | +For each of the top N modules (default 3), launch an Agent in parallel using |
| 119 | +`isolation: "worktree"` and `mode: "auto"`. All N agents must be dispatched |
| 120 | +in a single message so they run concurrently. |
| 121 | + |
| 122 | +Each agent's prompt must be self-contained and follow this template (adapt |
| 123 | +the module name, paths, and metadata): |
| 124 | + |
| 125 | +``` |
| 126 | +You are auditing the xrspatial module "{module}" for security vulnerabilities. |
| 127 | +
|
| 128 | +This module has {commits} commits and {loc} lines of code. |
| 129 | +
|
| 130 | +Read these files: {module_files} |
| 131 | +
|
| 132 | +Also read xrspatial/utils.py to understand _validate_raster() behavior. |
| 133 | +
|
| 134 | +**Your task:** |
| 135 | +
|
| 136 | +1. Read all listed files thoroughly. |
| 137 | +
|
| 138 | +2. Audit for these 6 security categories. For each, look for the specific |
| 139 | + patterns described. Only flag issues ACTUALLY present in the code. |
| 140 | +
|
| 141 | + **Cat 1 — Unbounded Allocation / Denial of Service** |
| 142 | + - np.empty(), np.zeros(), np.full() where size comes from array dimensions |
| 143 | + (height*width, H*W, nrows*ncols) without a configurable max or memory check |
| 144 | + - CuPy equivalents (cp.empty, cp.zeros) |
| 145 | + - Queue/heap arrays sized at height*width without bounds validation |
| 146 | + Severity: HIGH if no memory guard exists; MEDIUM if a partial guard exists |
| 147 | +
|
| 148 | + **Cat 2 — Integer Overflow in Index Math** |
| 149 | + - height*width multiplication in int32 (overflows silently at ~46340x46340) |
| 150 | + - Flat index calculations (r*width + c) in numba JIT without overflow check |
| 151 | + - Queue index variables in int32 that could overflow for large arrays |
| 152 | + Severity: HIGH for int32 overflow in production paths; MEDIUM for int64 |
| 153 | + overflow only possible with unrealistic dimensions (>3 billion pixels) |
| 154 | +
|
| 155 | + **Cat 3 — NaN/Inf as Logic Errors** |
| 156 | + - Division without zero-check in numba kernels |
| 157 | + - log/sqrt of potentially negative values without guard |
| 158 | + - Accumulation loops that could hit Inf (summing many large values) |
| 159 | + - Missing NaN propagation: NaN input silently produces finite output |
| 160 | + - Incorrect NaN check: using == instead of != for NaN detection in numba |
| 161 | + Severity: HIGH if in flood routing, erosion, viewshed, or cost_distance |
| 162 | + (safety-critical modules); MEDIUM otherwise |
| 163 | +
|
| 164 | + **Cat 4 — GPU Kernel Bounds Safety** |
| 165 | + - CUDA kernels missing `if i >= H or j >= W: return` bounds guard |
| 166 | + - cuda.shared.array with fixed size that could overflow with adversarial |
| 167 | + input parameters |
| 168 | + - Missing cuda.syncthreads() after shared memory writes before reads |
| 169 | + - Thread block dimensions that could cause register spill or launch failure |
| 170 | + Severity: CRITICAL if bounds guard is missing (out-of-bounds GPU write); |
| 171 | + HIGH for shared memory overflow or missing syncthreads |
| 172 | +
|
| 173 | + **Cat 5 — File Path Injection** |
| 174 | + - File paths constructed from user strings without os.path.realpath() or |
| 175 | + os.path.abspath() canonicalization |
| 176 | + - Path traversal via ../ not prevented |
| 177 | + - Temporary file creation in user-controlled directories |
| 178 | + Severity: CRITICAL if user-provided path is used without any |
| 179 | + canonicalization; HIGH if partial canonicalization is bypassable |
| 180 | +
|
| 181 | + **Cat 6 — Dtype Confusion** |
| 182 | + - Public API functions that do NOT call _validate_raster() on their inputs |
| 183 | + - Numba kernels that assume float64 but could receive float32 or int arrays |
| 184 | + - Operations where dtype mismatch causes silent wrong results (not an error) |
| 185 | + - CuPy/NumPy backend inconsistency in dtype handling |
| 186 | + Severity: HIGH if wrong results are silent; MEDIUM if an error occurs but |
| 187 | + the error message is misleading |
| 188 | +
|
| 189 | +3. For each real issue found, assign a severity (CRITICAL/HIGH/MEDIUM/LOW) |
| 190 | + and note the exact file and line number. |
| 191 | +
|
| 192 | +4. If any CRITICAL or HIGH issue is found, run /rockout to fix it end-to-end |
| 193 | + (GitHub issue, worktree branch, fix, tests, and PR). |
| 194 | + For MEDIUM/LOW issues, document them but do not fix. |
| 195 | +
|
| 196 | +5. After finishing (whether you found issues or not), update the inspection |
| 197 | + state file .claude/security-sweep-state.json by reading its current |
| 198 | + contents and adding/updating the entry for "{module}" with: |
| 199 | + - "last_inspected": today's ISO date |
| 200 | + - "issue": the issue number from rockout (or null if clean / MEDIUM-only) |
| 201 | + - "severity_max": highest severity found (or null if clean) |
| 202 | + - "categories_found": list of category numbers that had findings (e.g. [1, 2]) |
| 203 | +
|
| 204 | +Important: |
| 205 | +- Only flag real, exploitable issues. False positives waste time. |
| 206 | +- Read the tests for this module to understand expected behavior. |
| 207 | +- For CUDA code, verify bounds guards are truly missing -- many kernels already |
| 208 | + have `if i >= H or j >= W: return`. |
| 209 | +- Do NOT flag the use of numba @jit itself as a security issue. Focus on what |
| 210 | + the JIT code does, not that it uses JIT. |
| 211 | +- For the hydro subpackage: focus on one representative variant (d8) in detail, |
| 212 | + then note which dinf/mfd files share the same pattern. Do not read all 29 |
| 213 | + files line by line. |
| 214 | +- This repo uses ArrayTypeFunctionMapping to dispatch across numpy/cupy/dask |
| 215 | + backends. Check all backend paths, not just numpy. |
| 216 | +``` |
| 217 | + |
| 218 | +### 5c. Print a status line |
| 219 | + |
| 220 | +After dispatching, print: |
| 221 | + |
| 222 | +``` |
| 223 | +Launched {N} security audit agents: {module1}, {module2}, {module3} |
| 224 | +``` |
| 225 | + |
| 226 | +## Step 6 -- State updates |
| 227 | + |
| 228 | +State is updated by the subagents themselves (see agent prompt step 5). |
| 229 | +After completion, verify state with: |
| 230 | + |
| 231 | +``` |
| 232 | +cat .claude/security-sweep-state.json |
| 233 | +``` |
| 234 | + |
| 235 | +To reset all tracking: `/sweep-security --reset-state` |
| 236 | + |
| 237 | +--- |
| 238 | + |
| 239 | +## General Rules |
| 240 | + |
| 241 | +- Do NOT modify any source files directly. Subagents handle fixes via /rockout. |
| 242 | +- Keep the output concise -- the table and agent dispatch are the deliverables. |
| 243 | +- If $ARGUMENTS is empty, use defaults: top 3, no category filter, no exclusions. |
| 244 | +- State file (`.claude/security-sweep-state.json`) is gitignored by convention -- |
| 245 | + do not add it to git. |
| 246 | +- For subpackage modules (geotiff, reproject, hydro), the subagent should read |
| 247 | + ALL `.py` files in the subpackage directory, not just `__init__.py`. |
| 248 | +- Only flag patterns that are ACTUALLY present in the code. Do not report |
| 249 | + hypothetical issues or patterns that "could" occur with imaginary inputs. |
| 250 | +- False positives are worse than missed issues. When in doubt, skip. |
0 commit comments