security
Technical notes on web development, DevOps, and AI integration.
62 articles
- 19:14backend
Open Routes, Guarded Routes: Where the Permission Seed Goes
Admin routes return 403 for every role except superadmin because the permission rows were never seeded. The guard is only the enforcer.
TL;DR: Admin accounts got 403 errors due to missing permission rows. The permission middleware checked and denied access. Adding permission rows fixed the issue.
#go#security#mysql - 22:12backend
Throttling a Public Feedback Endpoint: Two Buckets and One Cap
A public feedback endpoint needs defenses on two axes: a body size cap before JSON decoding and two per-account rate limit buckets mounted after authentication.
TL;DR: A security review flagged a feedback endpoint accepting unlimited payload sizes and submission frequency. Fixes: cap the body at 32 KiB before JSON decoding, mount two per-user rate limit buckets (one per 10 minutes, five per day) after auth middleware, and return 429 with Retry-After. Also normalize nil query results to empty arrays so clients get the promised JSON contract.
#go#security#rate-limit - 19:55devops
Remapping Evidence Hashes After a Git History Scrub
Commit hashes change in bulk when git history is rewritten. Remap Bukti evidence references via commit-map, including zero-hash handling.
TL;DR: A PII history rewrite silently killed seven commit hashes stored as evidence on an issue board. Rewriting any commit changes its hash plus every descendant's, and only the commit-map file from git filter-repo reliably maps old hashes to new ones. Fix it by remapping and verifying each hash, then store future evidence as tags or exported mappings.
#git#git-filter-repo#commit-map - 19:06security
The Masked Public Roster: Ship Names, Not Phone Numbers
Hiding sensitive columns in the UI is not protection. The real mask lives in the API layer, proven by negative E2E assertions.
TL;DR: The article describes a common flaw where the UI hides sensitive fields but the API still sends full records to the client, violating OWASP API3:2023 and Indonesia's data minimization rules. The fix strips fields server-side via a projection DTO containing only public data. A Playwright test enforces negative assertions, ensuring PII never reaches the DOM.
#privacy#security#playwright - 23:02devops
Closing the Door Does Not Hide the Drawers
The deploy port was closed, yet /.git/ stayed readable through the staging hostname. Next: an nginx edge regex guard and an api/db network split.
TL;DR: Closing the inbound port didn't protect a staging server still serving .git and .env files over its tunnel. The fix needs three layers: nginx edge rules denying dot-paths, server_tokens off, and explicit Docker networks isolating services. Each layer covers what the others miss; any one alone leaves the same exposed drawers.
#nginx#docker#devops - 22:31devops
Frozen Staging Deploys: When a Tunnel Replaces the Ritual
The inbound deploy port is gone: KotaPortal staging is now served through an outbound-only tunnel, with no per-commit deploy ritual.
TL;DR: After a Cloudflare Tunnel took over backend staging, the per-commit deploy job disappeared, replaced by a persistent staging link that updates as the origin refreshes. The tunnel is outbound-only on port 7844, so the origin needs no public IP and loses its scannable deploy port, a real security win. Frontend keeps preview hosting; most 502s mean a wrong Service URL.
#cloudflare#cloudflared#deployment - 21:32security
Hardening the Next.js Image Optimizer Against SSRF
A wildcard hostname in remotePatterns turns /_next/image into an open server-side fetch proxy. The close-out pattern: local-only, one explicit host, a negative test.
TL;DR: KotaPortal's Next.js image config used a wildcard hostname in remotePatterns, turning the /_next/image endpoint into a potential SSRF vector since it fetches any HTTPS URL server-side. The fix removed the wildcard, allowing only same-origin images plus one explicit Google avatar host in both remotePatterns and CSP. A negative end-to-end test now verifies unlisted hosts get rejected.
#nextjs#next-image#ssrf - 12:58backend
A Deleted User Is an Authentication Failure: 401, Not 500
A cryptographically valid JWT can still name a user who no longer exists. Mapping sql.ErrNoRows to 401 lets the frontend purge the stale session.
TL;DR: A deleted user's JWT kept hitting the API, and since the middleware treated missing rows as a generic DB error, users got stuck in a 500 banner loop instead of being logged out. The fix uses errors.Is(err, sql.ErrNoRows) to return 401, letting the frontend purge the stale session. Tests now pin both paths.
#go#golang#jwt - 05:30security
A Mechanical Security Net Designed for Silence
A secret-scanning hook that separates findings from errors, non-blocking CI scanners, and a weekly ZAP baseline that skips gracefully.
TL;DR: gitleaks uses exit code 1 for both real leaks and scan errors, so naive hooks block commits over environment failures and get disabled. The fix: block only when exit 1 comes with a valid JSON report containing findings, warn otherwise. CI and weekly ZAP scans collect findings as reports instead of failing builds.
#security#gitleaks#github-actions