security
Technical notes on web development, DevOps, and AI integration.
62 articles
- 16:24backend
When a Feature Is Turned Off, Answer 404 Not 403
A disabled reviews feature answered 404 instead of 403. The HTTP spec actually backs that choice, and one cheap gate makes it practical.
TL;DR: Returning 403 for a disabled feature confirms the resource exists, inviting attackers to keep probing. Using 404 instead hides it completely, and a single service-layer flag can protect all related endpoints while the frontend hides UI via feature flags. For admin APIs though, 403 or 410 makes more sense since admins need to know the feature is just disabled.
#http#api#error-handling - 13:28backend
Three Small Fixes That Made the API More Honest
An empty note got blamed on the wrong field, a missing actor was recorded as zero, and email subjects carried raw submitter text. Three small fixes, one theme.
TL;DR: API returned a generic error for missing notes because the specific check was ordered last, so it now returns a clear note-required message first. Status updates now return 401 when user context is missing instead of silently recording the action as the system. Email subjects are also sanitized to strip control characters and prevent header injection.
#golang#api#security - 10:21devops
A Dev Quick Login That Never Becomes a Backdoor
Automating development login through the real form submit plus a fail-closed env guard: fast, with no backdoor left behind in production.
TL;DR: Repeated manual logins for testing were exhausting, and injecting tokens to bypass the form broke app state and caused flaky tests. Instead, automating the actual form submission behind an explicit opt-in env variable keeps it off by default. Paired with role-limited dev users and separate config, it speeds up testing without opening security holes in production.
#security#authentication#testing - 16:15security
Closing the Data Leak in an Automated Article Pipeline
Briefs headed to an external chatbot still carried hostnames and real repo names. A three-checkpoint mechanical gateway shut it.
TL;DR: Internal data was leaking to an external LLM, an architecture flaw not a prompt issue. The fix is a three-stage gateway that rewrites real identities to fictional ones before the model sees them, blocks pushes with real tokens, and scans published posts. It uses reserved example domains and doc IPs, and found seven leaks in 564 articles.
#security#prompt-injection#privacy - 15:55security
A Gitleaks Crash on a Multi-File Commit, and a Supply-Chain Gate Was Born
The secret-scanner hook crashed on a multi-file commit. One missing flag in the hook definition, and it ended with checksum-pinned CI.
TL;DR: The gitleaks system hook kept crashing on multifile commits because it mishandles the file list. Instead of waiting for a fix I switched to a local hook that runs a checksum verified binary on the staged diff. I apply the same strict pinning in CI with hashed downloads and minimal permissions accepting manual updates for stronger supply chain security.
#github-actions#security#ci-cd - 06:27backend
Setting Another User's Bidang: A Privileged Action
Only a superadmin may set a user bidang; the ALL default keeps 19 legacy users through the migration unharmed.
TL;DR: The schema change was simple, but deciding who can assign another user's scope was the real challenge. A central resolveBidang function lets only superadmins set it, returning 403 if others try while still allowing edits to other fields. Defaulting to ALL keeps existing users unlocked, with strict validation and eight tests locking the behavior.
#go#mysql#security - 06:27backend
Announcement Scope: Writing a Value vs Mutating a Row
Two authorization functions for per-division announcements: resolveBidang for new values, canManage for existing rows, NULL as global.
TL;DR: Portal now scopes announcements by division with two separate checks. resolveBidang governs creation, letting ALL admins write global NULL or any bidang while scoped writers are locked to their own. canManage governs existing rows, so only ALL can touch global content and labeled rows are owner-only, enforced in the service layer before any mutation.
#go#mysql#security - 05:05backend
Bidang RBAC Enforcement: The Middleware Gate and IN Filters
Enforcing the bidang scope in Go: middleware at the gate, IN filters in repositories, and subtests that read like the security policy.
TL;DR: Middleware at the gate verifies the JWT and injects the bidang scope so handlers stay clean. Repositories then filter by allowed modules using sqlx In, while announcements handle NULL as global, and global routes require ALL access. Leaving the repository fail-open keeps public calls simple, and subtests ensure scoped admins only see their own data.
#go#mysql#security - 04:57backend
Two-Axis RBAC: Roles for Actions, Bidang for Data
Roles decide actions, bidang decides data. Notes from building a fail-closed scope resolver in Go: ModulesFor, ResolveBidang, and the NULL trap.
TL;DR: Roles handle actions while a bidang column scopes data by agency, avoiding a huge permission matrix. A helper maps bidang to modules, returns nil for unknowns to fail closed, and copies the ALL slice to prevent Go append bugs. Announcements use NULL for global posts with an IS NULL filter, and empty bidang maps to ALL for legacy JWT compatibility.
#go#mysql#security