Why Behavior Allow-Listing Beats Endless Detection Rules in Container Security (Part 2)

This is Part 2 of our series on behavior allow-listing for container security. In Part 1, we explored the limitations of signature-based detection and introduced the concept of default-deny allow-listing. Now let's get practical.

Falco: An Example of Behavior-Based Detection

It's worth noting that the industry has been moving toward behavioral approaches for detection as well. Falco, an open-source runtime security tool (originally by Sysdig), is a great example of bridging the gap between raw signature scanning and full allow-list enforcement. Falco lives in the kernel (via eBPF or a kernel module) and watches syscalls in real time, looking for anything that isn't "normal" or allowed by a set of rules.

Falco comes with a bunch of default rules that represent suspicious behaviors in containers. These aren't signatures of known malware, but patterns of activity that are generally not expected in typical container workloads. For example, Falco has rules to detect if a shell is spawned inside a container with an atypical parent process. The rule (named "Terminal shell in container") is triggered when a process like bash or sh is launched by an application that normally wouldn't do that (Falco Rules Examples). In Falco's terms, "a shell was spawned by a non-shell program in a container" (Falco documentation) -- a strong sign of an interactive attack or escape attempt. Other rules look for things like writing to system directories, changing namespace outside of expected programs, or reading sensitive files within a container.

Falco's behavior-based detection is powerful: it doesn't matter if the attacker uses a brand-new exploit or a custom binary, if the outcome is that a shell is spawned or a sensitive file is accessed, Falco will catch it. It's a huge improvement over basic signature scanning, since it's rooted in behavior (what is happening) rather than identity (what something is known as). Many teams use Falco to get real-time alerts when something fishy happens in their Kubernetes clusters.

However, it's important to note Falco is primarily a detection tool -- it will notify you, but it doesn't block by itself. (You can integrate it with response tools to kill pods or processes, but that's an added step.) This is where allow-listing has an edge: instead of just raising an alert when, say, a shell appears, an allow-listing enforcement would have prevented that shell from ever starting in the first place by denying the exec() call for anything not explicitly permitted.

Think of Falco as the security camera that watches for intruders and sounds an alarm, whereas an allow-list profile (via AppArmor/SELinux) is like an automatic door lock that stops the intruder at the threshold. In practice, they can complement each other -- Falco can monitor and alert on things not covered by the allow-list or during development, and the allow-list provides the hard enforcement in production.

AppArmor in Action: Tailored Allow-Lists with bifrost

To make this more concrete, let's walk through how bifrost creates tailored allow-list profiles using AppArmor. AppArmor is a Linux kernel module that lets you define per-application (or per-container) security profiles. In enforce mode, the kernel will deny anything not explicitly permitted in the profile -- true default-deny security.

The key insight is that bifrost doesn't try to enumerate all the bad things that could happen. Instead, it learns exactly what your application actually does during normal operation and creates a precise allow-list of only those behaviors. Everything else is blocked by default.

Imagine we have a simple web application container. During bifrost's profiling phase (which runs in your CI/CD pipeline or during testing), it observes the container's actual behavior: which files it reads, which executables it runs, which network connections it makes, which syscalls it uses. For our web app, bifrost might observe that it reads config files from /etc/myapp/, executes only /usr/bin/node, connects to a database on port 5432, and uses a specific set of syscalls.

From this learned behavior, bifrost automatically generates a tailored AppArmor profile that allows only what was observed:

# Allow only the specific executable the app needs
/usr/bin/node ix,

# Allow reading only the config directory
/etc/myapp/** r,

# Allow reading application files
/app/** r,

# Allow network access only on required ports
network tcp,

# Allow only the specific syscalls observed during profiling
# (handled via seccomp integration)

Notice the fundamental difference from traditional "blocklist" approaches: we're not writing rules to block /bin/bash or /var/run/secrets/. Those paths simply aren't in the allow-list, so they're blocked by default. The profile doesn't need to anticipate every possible attack vector -- if it wasn't part of normal application behavior, it's not allowed.

This is the power of bifrost's approach. Because the profile is generated from actual runtime observation, it's both comprehensive (covering everything the app legitimately needs) and minimal (excluding everything it doesn't). An attacker who exploits a vulnerability in your app will find themselves in an extremely constrained environment: they can't spawn shells (not in the allow-list), can't read Kubernetes secrets (not in the allow-list), can't download additional tools (not in the allow-list), and can't make unexpected network connections (not in the allow-list).

Contrast this with a detection rule: Falco, for instance, would detect "hey, a shell started in the container" and alert you. But by the time you get that alert, the shell is already running (even if just for a moment) and the attacker might have poked around. With bifrost's allow-list enforcement, the shell never runs at all -- /bin/bash simply isn't permitted. In security parlance, it's the difference between prevention and detection.

AppArmor and similar MAC (Mandatory Access Control) tools essentially implement behavior allow-lists at the OS level. Kubernetes supports applying AppArmor profiles to Pods via the securityContext. Many managed Kubernetes offerings include a default profile (like Docker's docker-default) that provides a modest baseline confinement. AppArmor allows you to control what processes can access, even as root. But the default profile is usually not very strict, to avoid breaking legitimate app behavior. Creating truly tailored profiles manually is notoriously difficult and time-consuming -- you'd need to trace every file access, every syscall, every network connection your app makes. That's exactly what bifrost automates.

To illustrate the outcome: when a bifrost-generated profile is active, any action outside the learned behavior is blocked and logged by the kernel. If an attacker inside the container tries to run cat /var/run/secrets/token.txt, that path isn't in the allow-list, so the open system call is denied. If they try to execute /bin/bash, it's not an allowed executable, so it fails to launch. Security logs would show AppArmor denials, but more importantly, the attack is contained before it can do damage. The attacker finds themselves confined to exactly what your application was designed to do -- nothing more.

Fewer Alerts, Smarter Responses

One of the biggest operational advantages of behavior allow-listing is the reduction of noise. With signature-based detection, it's common for teams to get inundated with alerts -- many of which turn out not to matter. For example, a vulnerability scanner might flag 100 CVEs in a container image, prompting alarms, while in reality 95 of those CVEs are in packages your application never touches (perhaps they're in an installed but unused library). This leads to wasted effort trying to patch or investigate issues that pose no real risk, or simply tuning out alerts because "there are too many".

Allow-listing changes this dynamic. Because the approach narrows down what's permitted, any alert or block tends to be something truly unusual. Security engineers can respond with high confidence that a violation is worth investigating. This minimizes alert fatigue and lets the team concentrate on real threats. As our platform emphasizes, focusing on just the unexpected behavior means your team isn't chasing ghosts -- they're dealing with concrete anomalies.

Moreover, allow-listing can effectively future-proof your defenses against unknown threats. You don't need an update when a new CVE or exploit technique comes out -- if that exploit would cause the application to step out of its normal bounds (which most attacks do), it'll be caught. For example, suppose a new container escape technique is discovered that involves writing to /proc/sys/... (a kernel interface) in a certain way. If your profiles don't allow containers to write to those sysctl paths, that entire class of attack is already neutralized for you, even if you never heard of the CVE. In contrast, a signature-based system would need to add a rule or pattern for this specific behavior to detect it.

Another benefit is the improvement in response and patching workflows. When you have allow-lists in place, you gain breathing room for remediation. If a critical vulnerability in your application or base image is disclosed, normally you'd scramble to patch or rebuild images immediately, fearing an active exploit. But if your runtime allow-list is solid, you know that even if attackers try to hit that vulnerability, any abnormal actions will be blocked. bifrost's team recently highlighted this during a Redis RCE (CVE-2025-49844) advisory -- their customers' Redis containers were already protected by tailored allow-profiles, so an attacker exploiting that bug "cannot spawn shells, run arbitrary binaries, or touch anything outside Redis' legitimate paths and syscalls". Those organizations were effectively shielded from the attack, buying them time to patch in a calm, controlled way without emergency downtime. This is a huge operational win.

Signature-based detection, on the other hand, might alert you that someone executed the exploit (if it even recognizes it), and then you're in full-on incident response mode. Allow-listing strives to prevent the incident from escalating in the first place.

bifrost's Approach: Automatic Profiling and CVE Context

Implementing allow-listing manually for every container can seem like a daunting task. How do you determine the allowed syscalls, file accesses, and network connections for each microservice, and keep that updated as the app changes? This is where automation and intelligent tooling come into play -- and it's exactly the problem bifrost set out to solve.

Out platform takes the concept of behavior allow-listing and makes it continuous and automated. It integrates into your CI/CD pipeline and monitors your applications during testing or runtime to learn their normal behavior. Essentially, it profiles each new container build or version, observing what files it touches, what executables it runs, which syscalls and network connections it uses, etc. From this, bifrost automatically generates an AppArmor profile (or other LSM profile) tailored to that exact workload. This happens for every new release -- so your security policies evolve in lockstep with your application. The next time you deploy, there's no extra work; bifrost has already prepared an updated allow-list profile reflecting any new legitimate behaviors (and nothing more).

This approach means you don't have to manually write or update AppArmor profiles -- a task that can be very tricky and time-consuming if done by hand. As managing those profiles across dozens of services in a dynamic Kubernetes cluster is error-prone and burdensome. bifrost automates that heavy lifting. With just a few lines of configuration in your deployment (to enable the bifrost agent), every container gets continuous protection, without code changes or slowing down your pipeline. That's all it takes to enable bifrost in your current security stack. Every new build inherits a fresh, adjusted allow-list, so security is always up-to-date even in rapid release cycles.

Another standout advantage is how we (and tools like it) handle vulnerabilities. Instead of giving you a giant list of CVEs from a static scan, bifrost correlates those CVEs with the runtime behavior of your service. This means it can highlight which vulnerabilities are in code paths your container actually executes, and which ones are in dormant code never touched in production. Why is this important? Because it helps prioritize remediation and cuts through the noise of theoretical risks.

Consider a scenario: your container image has 50 known vulnerabilities. A traditional scanner will report all 50. But bifrost's runtime intelligence might find that only 2 of those correspond to libraries or functions that your application loads or calls at runtime -- the other 48 might be in components that are installed but not used. By mapping CVEs to actual runtime usage, you can focus on the 4% that truly matter and not panic over the rest. In fact, industry analysis by similar runtime security approaches has found that typically only a tiny fraction of vulnerabilities in an image are ever reachable in practice-. bifrost's "CVE Intelligence Engine" works on the same principle: it shows you which CVEs map to functions your app actually hits.

Finally, bifrost doesn't just detect unexpected behaviors -- it blocks them in real time. Its philosophy is "detect and prevent," enforcing intended behavior. so that unexpected bad behavior is effectively blocked from occurring. In practice, if an attacker triggers a vulnerability that tries to break out of a container or escalate privileges, nifrost's allow-list enforcement (via the kernel) will stop that action cold. This moves security from a passive monitoring stance to an active defense mode, all while integrating smoothly into DevOps workflows.

Conclusion: Narrowing the Path to Attack

In an age of Kubernetes and microservices, where deployments are frequent and change is constant, leaning on an ever-growing library of detection rules is a losing battle. Behavior allow-listing offers a smarter path. By narrowing the acceptable behavior of each container to its known-good pattern, you drastically reduce the avenues through which attackers can operate. Default-deny profiles act as a defensive wall around your workloads -- anything not explicitly allowed is immediately flagged or blocked, whether it's a known threat or something never seen before.

For DevOps teams and developers, this means fewer noisy alerts to chase and more trust in the infrastructure's security. Instead of reacting to endless "possible threat" alerts (or silencing them and hoping for the best), you can rely on the fact that if something malicious starts happening, it's genuinely anomalous -- and likely being stopped in its tracks. It's a shift from playing catch-up (with signatures and patches for every new exploit) to proactively containing whole classes of attacks by design.

The example of using Falco alongside allow-listing illustrates a defense-in-depth strategy: Falco gives visibility into unusual events (which is great during development and testing to fine-tune what "normal" is), and the allow-list enforcements in production ensure that those unusual events can't cause harm. Meanwhile, tools like bifrost bring these concepts to the enterprise in a streamlined way -- automatic profiling, continuous updates, and meaningful vulnerability context -- so that robust container security doesn't slow down innovation.

In the end, the goal is to let DevOps move fast without constantly looking over their shoulder for security issues. Behavior allow-listing, done right, provides a kind of autopilot for runtime security: lock in what should happen, and let the system handle the rest. Fewer alerts, fewer surprises, and resilient containerized applications that can withstand even the unknown threats. That's a win for developers, security engineers, and the business as a whole.

Next
Next

Why Behavior Allow-Listing Beats Endless Detection Rules in Container Security (Part 1)