April 14: Hardening Spree
Thirteen-PR security and correctness blitz — auth rate limiting, account lockout, user enumeration prevention, data enumeration prevention across analysis and composite endpoints, NaN and empty-input guards across the processing pipeline, and a handful of race-condition and validation bugs.
Developer Journal
Auth rate limiting + lockout + enumeration prevention (#1187)
The big one. Three related changes in the auth flow:
Rate limiting — /api/auth/login and /api/auth/register now have per-IP rate limits (5 attempts per minute, 20 per hour) via Microsoft.AspNetCore.RateLimiting. Returns 429 with retry-after when exceeded.
Account lockout — after 5 failed login attempts on a single account within 15 minutes, the account locks for 30 minutes. The lock only applies to that account, so one attacker can't lock everyone out by enumerating usernames.
Enumeration prevention — both login and registration now return identical response shapes and timings for "user exists" vs "user does not exist" vs "wrong password." Previously, login returned different response bodies for "user not found" vs "password wrong," which let an attacker enumerate valid usernames. Now it's always the same generic message with constant-time password checks against a dummy hash when the user doesn't exist.
The constant-time trick is important — if the response time differs by 100ms depending on whether the user exists, that's a side channel even if the response body is identical. Added tests that measure timing and assert it's within a tight tolerance for both cases.
Prevent data enumeration via analysis and composite endpoints (#1174)
Separate from the auth fix, the analysis and composite endpoints were returning different errors for "not found" vs "not shared with you." An attacker who shouldn't have access to target X could still learn that X exists by seeing "not shared" instead of "not found." Normalized to always return 404 regardless — the server knows which one is true, the client can't tell.
NaN and empty-input guards (#1176, #1181, #1183, #1177)
Four PRs hardening the processing pipeline inputs:
- #1176 —
normalize_array()was returning NaN when given all-NaN or empty input. Now guards up front, raisesValidationExceptionwith a clear message. - #1181 —
create_region_mask()was building masks from coordinate pairs without bounds-checking. Added checks that RA/Dec are in range and the radius is positive. - #1183 — similarity search was returning NaN scores when one side of the comparison had no features. Now filters NaN scores out before ranking and returns empty results with a warning log rather than a corrupted ranking.
- #1177 —
downscale_for_composite()was acceptingmax_pixelswithout validating the target size would fit in memory. Added explicit validation that rejects unreasonable targets early.
Pagination and file handling fixes
- #1180 — MAST service pagination was applying
limitbeforeoffset, returning a smaller-than-requested page when offset was non-zero. Fixed order. - #1182 —
resolve_s3_keys_from_products()was crashing on products missing required fields. Added explicit validation that rejects malformed products with a clear error rather than an opaque AttributeError. - #1178 — partial file mtime access was racing against the writer. The reader could hit the file between fopen and first write, get mtime 0, and misclassify as stale. Added retry with backoff.
- #1179 — search results were missing the SharedWith data on the target DTO, so the UI couldn't render the "shared with" badge. Wired it through.
Cache eviction hardening (#1184, #1185)
- #1184 —
temp_cacheeviction was firing but not verifying that eviction actually freed the target amount of space. On a shared filesystem where other processes could be holding file handles, eviction could succeed logically but fail physically. Now logs the before/after free space and warns if eviction didn't achieve the goal. - #1185 —
cacheUtilson the frontend had a retry loop with no upper bound. If eviction kept failing (disk full and browsers refusing to evict), the tab would freeze. Capped at 3 retries with exponential backoff, then surface the error to the user.
Observe background tasks in MastController (#1188)
Background Tasks kicked off from controllers were being fire-and-forget — if one threw, the exception went to TaskScheduler.UnobservedTaskException and was silently logged at best. Wrapped all the fire-and-forget tasks in _ = SafeExecuteAsync(() => ...) which logs with the controller context and the job ID, and reports to the job tracker if the task was tied to a job. No more silently dropped background work.
Side note: Claude Desktop terminal integration
Claude Desktop got a significant overhaul — it can now run local sessions directly with terminal integration, which makes some workflows nicer than the CLI for interactive exploration. Skills work there but hooks don't, so it's a complementary tool rather than a replacement. A friend joked about how long until the AI decides rm -rf solves all debug issues; the honest answer is "trust but verify, and never bypass permissions mode."
What shipped
| PR | Title |
|---|---|
| #1188 | fix: observe background tasks in MastController to prevent silent failures |
| #1187 | feat: add auth rate limiting, account lockout, and user enumeration prevention |
| #1185 | fix: bound eviction retry loop in cacheUtils to prevent tab freeze |
| #1184 | fix: log and verify cache eviction results in temp_cache |
| #1183 | fix: guard against NaN scores and empty results in similarity search |
| #1182 | fix: validate required fields in resolve_s3_keys_from_products |
| #1181 | fix: validate coordinate bounds in region mask creation |
| #1180 | fix: apply limit after offset in MAST service pagination |
| #1179 | fix: include SharedWith data in search results |
| #1178 | fix: handle race condition when accessing partial file mtime |
| #1177 | fix: validate max_pixels and array dimensions in downscale_for_composite |
| #1176 | fix: guard normalize_array() against empty and all-NaN arrays |
| #1174 | fix: prevent data enumeration via analysis and composite endpoints |