I gave a talk at a GitLab virtual event with Nico Meisenthal from White Duck and Philippe Lafourcière, a distinguished engineer at GitLab. The premise was simple: show, don’t tell. We picked a real (if deliberately vulnerable) application and walked the audience through an attack and response in real time.

Here’s what we built and what it taught me.
The application
Nico put together a small Go app running in Azure Kubernetes Service. It had one job: accept an IP address or hostname, ping it, and show you the output. Clean, simple, and as it turned out, broken in a predictable way.
The app passed user input directly to the ping command without validation. That’s a command injection vulnerability, and it’s as old as the web itself.
What the attacker did (that was me)
I played the attacker. Starting from that one flaw, I was able to:
- Confirm the app was running as root by typing
; whoamiafter an IP address - List all keys in a Redis instance running in a separate Kubernetes namespace
- Read values out of that Redis store
- Overwrite them
None of that required anything exotic. One semicolon and a few Redis CLI commands. The app was in production, running as root, with no network policy restricting where it could talk.
The bad actor only needs to find one way in. The defenders have to cover everything. That asymmetry is the whole problem.
What the security team saw
Philippe monitored from the security operations side. GitLab’s threat monitoring page flagged an unusual spike in requests almost immediately. He pulled the WAF logs from the ingress controller using kubectl and saw the Redis CLI traffic in the mod_security sidecar logs.
Then he checked container host security, which uses Falco under the hood. The logs showed a new process spawning inside the application container, running as root, reaching across into the Redis namespace. At that point, the picture was clear: remote code execution, root-level access, cross-namespace traffic.
Four problems stacked on top of each other:
- Insecure application in production
- Remote command execution running as root
- WAF in logging mode, not blocking
- No network policy restricting cross-namespace traffic
What remediation looked like
Nico walked through the fixes live. He enabled a default network policy in GitLab’s container network security tooling that denied egress traffic to the Redis namespace. Next time, the attacker tried to reach Redis, the connection timed out at the pod level.
Then he switched the WAF from logging to blocking mode. The same attack that had been working minutes earlier returned with a 403 Forbidden response from the ingress layer. The request never touched the application.
On the code side, GitLab’s SAST scanner had already flagged the issue. The finding showed up in the security dashboard under subprocess command construction with external input — medium severity, pointing directly to line 37 in main.go. Nico created an issue from it on the spot.
The takeaway
What struck me about this demo, even though I had helped put it together, was how quickly the blast radius grew from a single missed input check. One unvalidated field led to root-level access to a data store in a different namespace, in under a minute.
Defense in depth isn’t a compliance checkbox. It’s what gives you a second and third chance when the first layer fails, and first layers fail. The network policy stopped the attack even before the WAF was switched to blocking. The SAST scanner had the root cause flagged before anyone looked. Logs from Falco made it possible to understand exactly what happened.
Design your applications to validate input. Run your scanners and actually look at what they find. Default-deny your network policies. Know what your containers are doing at runtime.
And if you’re lucky, when someone does find a way in, you’ll have the visibility to catch it before they do anything interesting with it.

Leave a Reply