A honeypot that only listens (and fail2ban that bites)
A logging-only honeypot on my Hetzner box: an unprivileged user, a systemd sandbox with one non-obvious trap that silences it, and fail2ban to turn every knock into a self-expiring firewall ban.
Topic
Attacking my own features and reading the protocols underneath them — secrets delivery, response headers, a published address for bad news, a cookie banner that sets no cookies, and what an auth flow is actually protecting you from.
A logging-only honeypot on my Hetzner box: an unprivileged user, a systemd sandbox with one non-obvious trap that silences it, and fail2ban to turn every knock into a self-expiring firewall ban.
I froze the redirect Spring sends you through and read it parameter by parameter. Three lookalike strings turned out to close three different attacks, and PKCE was already on without me asking for it.
RFC 9116 answers the one question a stranger needs answered about your site: who do I tell about a vulnerability. What the file must contain, why the expiry date belongs in a test, and the routing trap that makes an SPA host serve it as HTML at 200.
I put an MCP server on the public internet so an AI client could file todos onto my private kanban board. The interesting part wasn't the protocol — it was making the dangerous call unrepresentable.
My kanban board had no authentication at all and handed its entire database to any caller. Putting it on focus.zakaria.lu meant a fourth host on the jar that was already running - plus a disclosure bug caught in review, and one cookie dot that 500'd every sign-in.
I added a privacy notice to my site and refused to make it a cookie banner — because the only cookie I set is a functional one. Here's the reasoning, and why the dismiss flag lives in localStorage, not a cookie.
The cheapest security win on a single-container stack lives at the reverse proxy. Here's the four-header Traefik middleware I shipped, the two things I deliberately held back, and the report-only CSP rollout I'm lining up next.
Five ways to hand a secret to a Docker container, ranked by who can read it back out. The source is just plumbing - environment variable vs file is what actually decides your exposure.
Posts that touch this subject but live under another topic.
I applied for verified Daybreak access, completed the identity check, and mapped the next step: connecting my own GitHub repositories to Codex Security without giving up human review or scope control.
I had Anthropic's always-on security plugin installed for a month and assumed it was reviewing every turn. Its own log said otherwise. What each layer actually does, how far its configuration goes, and the one hook that says no.
Putting a self-hosted identity server in front of LinkedIn deleted 240 lines of workaround and turned PKCE back on. It also handed out a privilege escalation — because the claim never changed, but the trust underneath it did.
Shipping an AI feature into my own product · Part 4
Adding browser dictation to my AI concierge took twenty minutes. Making it behave in a real composer, refuse to leave a microphone running, and tell the truth about where the audio goes took the rest of the day.
I pointed the new claude-security plugin at this very site. It spawned 82 agents, proposed 10 vulnerabilities, and then argued itself out of 9 of them. The one that survived was real — and it shipped to prod the same afternoon.
Shipping an AI feature into my own product · Part 3
I added job-description matching to the AI concierge on my career site. It shipped with no new bean, no new table, and no migration — because the leverage was in a seam I'd already built. A design note on shared streaming cores, streamable structured output, and treating a pasted JD as hostile.
Shipping an AI feature into my own product · Part 2
My site has an 'Ask Zakaria' AI box that answers untrusted recruiter input. So I spent an afternoon trying to break it — prompt leaks, tool-call injection, XSS, a cross-language jailbreak. Here's what held, and why the real defense isn't a clever prompt.