Skip to content

Commit 476e0c5

Browse files
authored
Add /sweep-security command for security vulnerability audits (#1193)
New slash command that audits xrspatial modules for security issues specific to numeric/GPU raster libraries. Dispatches parallel subagents to check 6 categories: unbounded allocations, integer overflow in index math, NaN/Inf logic errors, GPU kernel bounds safety, file path injection, and dtype confusion. Follows the same single-phase pattern as /sweep-accuracy: score modules by security-relevant metadata (CUDA kernels, file I/O, allocations from dimensions), dispatch worktree-isolated subagents for the top N, and fix CRITICAL/HIGH issues via /rockout.
1 parent b7e7d1e commit 476e0c5

1 file changed

Lines changed: 250 additions & 0 deletions

File tree

.claude/commands/sweep-security.md

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
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

Comments
 (0)