security
Technical notes on web development, DevOps, and AI integration.
62 articles
- 13:50backend
From Full Buffering to Streaming Uploads in Go
Five slots times 10 MiB per request is a DoS invitation. A per-user rate limit plus multipart streaming cuts memory to roughly 1 MiB per request.
TL;DR: Buffering five 10 MiB uploads per request risked gigabytes under concurrent load, since a per-request cap doesn't limit request frequency. A per-user rate limiter of 30 requests per minute returns 429 with Retry-After to cap waves. Streaming with a 1 MiB memory window keeps each request near 1 MiB regardless of file size.
#go#security#upload - 03:53tooling
The Auditor Uses the Same Ruler as the Gate
An audit report accused eight documentation commits of missing trailers; all eight were false positives. One filter line aligned the auditor with the gate.
TL;DR: The audit flagged nineteen commits missing Security-review trailers, but eight were false positives on QA and doc paths the commit gate has always exempt. Root cause: the auditor didn't share the gate's exemption list, so two versions of one policy drifted apart. Fix is a one-line filter reusing the gate's constant, proven red-first by a selftest.
#git#bash#testing - 03:53security
The Read-Only Audit: The Missing Half of a Commit Gate
The commit gate refuses a trailer-less message in seconds, but the configuration behind it drifts silently. A read-only sweep closes that gap.
TL;DR: The commit gate enforces a Security-review trailer but can silently vanish through a lost executable bit or a moved hooksPath. A read-only audit subcommand in review_gate.sh sweeps commits, warnings, slice-gate results, and registration, always exiting 0. The gate prevents, the audit detects; together they keep the invariant honest.
#git#bash#security - 01:00backend
The filter that stayed silently empty
Three public failures in one night: a filter that never matched, search exploding to 500, and a 403 echoing input.
TL;DR: A broken anchored LIKE pattern made the amenity filter always return empty rows, fixed by switching to substring matching with wildcards escaped. An unsanitized search term crashed MySQL's full-text parser via a single asterisk, fixed by stripping operator characters at the boundary. A 403 response echoed user input back to attackers, fixed by replacing it with a static message while logging the raw value.
#mysql#sql#input-validation - 22:45security
An Empty Scope Is Not Everything: Fail-Closed Choreography
An empty scope column used to mean ALL for legacy tokens. The four-step choreography that flipped the middleware to deny 403 BIDANG_UNSET.
TL;DR: An empty bidang scope was silently treated as ALL, a classic failing-open bug that let unscoped accounts pass authorization. Retiring it took choreography: backfill data, kill the shim's producers, prove a census of zero empty rows, then flip middleware to deny with 403 BIDANG_UNSET. Two denial layers and one documented exception (system actor userID 0) make the deny-by-default contract auditable.
#go#security#rbac - 22:45tooling
One Door, One ID: An Audit Tool That Cannot Be Fooled
A review flagged read() following symlinks into .git and a lint matching IDs across file boundaries. What an evidence verifier owes its own integrity.
TL;DR: An audit tool review found two bugs. Its read() helper followed symlinks anywhere, so it now rejects all symlinks outright, dodging the race between resolving and opening. Also, joining lint corpus files with an empty string fused IDs across file boundaries, causing false matches; a newline separator fixed that.
#python#symlink#qa - 21:24backend
When the Guard Cannot Answer: Fail-Closed Media Deletion
A failing usage census used to count as permission to delete. Now ErrCensusUnavailable cancels the delete: row and file survive, client gets 503.
TL;DR: Media deletion used to fail open: if the usage-count query errored, the code assumed the file was safe to delete. The fix returns ErrCensusUnavailable, mapped to a 503 CENSUS_UNAVAILABLE response, with real details logged server-side per OWASP guidance. Tests now verify deletion is blocked, data stays intact, and no stray files are written.
#golang#error-handling#http - 20:45testing
Coverage Percentage Is Not a Shield: Lock the Reject Arms
A Go middleware coverage audit from 27 to 96.7 percent: what httptest arm-by-arm reject-path testing teaches, from JWT to CORS.
TL;DR: A coverage audit showed middleware at 27 percent despite guarding authentication, and the untested paths were all security-critical rejections. Walking arm by arm with Go's httptest added 850 lines across seven files, pushing middleware to 96.7 percent without changing production code. Coverage stops climbing where fakes start lying, so each rejection arm now gets a named test immediately.
#golang#testing#security - 14:52tooling
Two-Tier Law in a Commit Hook at KotaPortal
A commit-hook script keeps two tiers of law: broken test-marker claims block the commit, while risky surfaces like auth paths or destructive migrations only earn a logged warning.
TL;DR: The commit script has two tiers: a test marker like "characterization" hard-blocks the commit unless an exact comment line is present, while risky surfaces such as auth code, destructive migrations, or dangerouslySetInnerHTML only log a warning. Hard-blocking everything would kill velocity, and hooks can be bypassed with --no-verify. The goal is raising the cost of carelessness, not building a wall.
#git#git-hooks#security