|8 min read|Yvann Lièvre

Detecting Container Cryptojacking with Falco and Tetragon

Cryptojacking Kubernetes Falco detection: the rule that catches a binary launched from /tmp, mining pool connections, and what still needs a human before a kill

FalcoTetragonCryptojacking
Detecting Container Cryptojacking with Falco and Tetragon

A container pinned at 100% CPU for hours is not necessarily processing a legitimate workload. It is often the most visible symptom of cryptojacking: a cryptocurrency miner planted by an attacker, quietly running on infrastructure that is not theirs. The real entry point is almost never a knowingly malicious image pushed to a public registry. It is far more often a legitimate, popular image compromised through an unpatched CVE, exploited from the outside, then used to drop and launch a mining binary from /tmp. The question a security lead faces in front of an unexplained CPU spike is simple: is one of my containers mining crypto for an attacker, and which Falco rule catches it without drowning the team in noise?

The real scenario: from compromised image to miner

The chain almost always follows the same pattern. A publicly exposed service (an API, a web application, a poorly isolated endpoint) runs in an image where a dependency carries a known CVE, often with a public exploit available. The attacker gets code execution inside the container, downloads a mining binary (XMRig and its derivatives show up most often) into a writable location such as /tmp or a mounted volume, then launches it. The binary connects to an external mining pool over the Stratum protocol and burns the container's allocated CPU for days, sometimes weeks, before anyone notices on a cloud bill or a monitoring dashboard.

Three technical signals let you cut that chain long before the invoice arrives: execution from an unusual directory, network connections to a mining pool, and correlation between a CPU spike and an unexpected child process on an exposed container. None of the three alone is enough to conclude without noise. Together, they form a reliable detection.

Falco rule: execution from /tmp or a mounted volume

Falco watches system calls at the kernel level and exposes the spawned_process event for every new process. The most direct signal of a miner starting up is an executable whose path (proc.exepath) sits in /tmp, /var/tmp, or a volume mounted into the container, which is almost never legitimate application behavior in production:

- rule: Binary executed from a writable container directory
  desc: >
    Detects execution of a binary from /tmp, /var/tmp, or a volume
    mounted inside a container, a frequent signal of a cryptocurrency
    miner being dropped and launched after compromise.
  condition: >
    spawned_process
    and container.id != host
    and (proc.exepath startswith /tmp
         or proc.exepath startswith /var/tmp
         or fd.name startswith /tmp)
    and not proc.name in (allowed_tmp_binaries)
  output: >
    Suspicious execution from a temporary directory
    (container=%container.name image=%container.image.repository
    proc=%proc.name exepath=%proc.exepath cmdline=%proc.cmdline)
  priority: WARNING
  tags: [cryptomining, container, process]

The not proc.name in (allowed_tmp_binaries) clause is not decoration: without that exclusion, the rule fires on any build image that compiles or unpacks artifacts into /tmp, which is perfectly normal behavior for CI pipelines or multi-stage images. More on that in the false-positive section below.

Detecting outbound connections to a mining pool

A running miner has to talk to a pool to submit its computed shares. The most common protocol is Stratum (ports 3333, 4444, 5555, 7777, 8080 depending on the pool, often raw TCP or TLS over non-standard ports). Two approaches combine well: a Falco network rule on outbound connections, and enrichment through an IOC feed of known pool domains and addresses.

- rule: Outbound connection to a known mining pool
  desc: >
    Detects an outbound network connection from a container to a
    domain or IP address associated with a known mining pool, or to
    a typical Stratum port with no expected application context.
  condition: >
    outbound
    and container.id != host
    and (fd.sip.name in (known_mining_pool_domains)
         or fd.rport in (3333, 4444, 5555, 7777, 8080)
         or fd.sip in (mining_pool_ip_ioc_list))
  output: >
    Outbound connection to suspected mining pool
    (container=%container.name proc=%proc.name
    dest=%fd.sip.name:%fd.rport cmdline=%proc.cmdline)
  priority: CRITICAL
  tags: [cryptomining, network, exfiltration]

The mining_pool_ip_ioc_list needs to come from a maintained, dated IOC feed, not a static list copied once and never refreshed: pools rotate infrastructure regularly, and a frozen list goes stale within months. This is continuous curation work, not a rule you write once and forget.

Correlating a CPU spike with an unexpected child process

An exec from /tmp on its own can be a tooling false positive. A connection to port 3333 on its own can be a test or a badly named legacy service. What raises confidence is the correlation: a publicly exposed container (so with a direct attack surface) that, outside any deployment window, spawns a child process not accounted for by its base image, at the same time as a sustained CPU spike over several minutes.

# Manual triage on the orchestrator side: exposed containers
# with sustained CPU above 90% and a child process not present
# in the base image
kubectl top pods --containers --sort-by=cpu \
  --field-selector metadata.namespace=production
 
# Cross-check: does the pod have a publicly exposed service?
kubectl get svc -n production -o wide | grep LoadBalancer

It is that correlation, unexpected exec plus Stratum connection plus CPU spike on an exposed surface, that separates a miner from routine exploitation noise, and that justifies treating the alert as critical rather than as a single log line.

Avoiding false positives: do not blindly alert on every /tmp exec

This is where most Falco deployments generate too much noise and get ignored. Three exclusions turn the rule into something usable in production:

  • Legitimate build images. Images that compile, unpack archives, or install dependencies at startup (init containers, CI jobs) write to and execute from /tmp routinely. Scoping the rule to exclude build-dedicated labels or namespaces (namespace != ci-build, container.image.repository not in (build_images)) removes most of the noise.
  • Scoping by namespace and label. A rule that applies uniformly across the whole cluster treats a dev environment the same as an exposed production one. Restricting severity or activation by label (k8s.ns.label.environment == "production") concentrates attention where the risk is real.
  • An allowlist of known binaries. Some legitimate observability tools or security agents drop their own temporary binaries. A short, explicit, documented allowlist beats a rule that ignores all of /tmp by default.

A rule that alerts on every /tmp exec without these three filters is not detection, it is a ticket generator that eventually gets disabled. The proof of its quality comes from running it against a benign corpus representative of your own images before going to production, not from a gut feeling that it "looks right."

Falco for detection, Tetragon for enforcement

Falco observes and alerts, but it does not act at the kernel level by default. For enforcement, meaning blocking or killing the process at execution time, Tetragon (eBPF, part of the Cilium project) lets you write an equivalent policy with an active blocking action:

apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: block-exec-from-tmp
spec:
  kprobes:
  - call: "security_bprm_check"
    syscall: false
    args:
    - index: 0
      type: "linux_binprm"
    selectors:
    - matchBinaries:
      - operator: "Prefix"
        values:
        - "/tmp/"
        - "/var/tmp/"
      matchActions:
      - action: Sigkill

Moving from a Falco rule (observation) to a Tetragon policy (automatic kernel-level blocking) is an enforcement decision, not a simple configuration toggle. Before turning on automatic kill mode, you need to have run the rule in observe-only mode for long enough on the target environment, and measured its false-positive rate against real traffic from your build images and legitimate jobs. A poorly scoped Tetragon Sigkill policy can interrupt a legitimate CI pipeline just as effectively as it can stop a miner.

Which signals justify an automatic kill, which stay human-in-the-loop

Not every signal deserves the same automated response:

  • Defensible automatic kill: a confirmed connection to a mining pool identified through a dated IOC feed, combined with an exec from /tmp on a container that has no functional reason to execute anything there (an application production image, not a build one). Combining both signals sharply reduces the risk of a false positive.
  • Stays human-in-the-loop: an isolated /tmp exec with no associated suspicious network connection; a CPU spike with no unexpected child process identified; any alert on a build, CI, or development namespace where writing to /tmp is normal, expected behavior.

Automatic kill is only justified when the combination of signals clearly exceeds the baseline noise measured on your own benign corpus. In every other case, the alert should go to an operator who validates before any remediation action, particularly on production containers where an unplanned stop carries a real business cost.

In summary

Detecting container cryptojacking does not rest on one magic rule: it is the combination of an execution signal (/tmp, mounted volume), a network signal (mining pool via a dated IOC feed), and a correlation signal (CPU spike on an exposed surface) that produces an alert worth acting on without drowning the team. Falco covers observation, Tetragon enables kernel-level enforcement once confidence is proven, not assumed.

That is exactly the method, rules validated on the real engine, false positives proven against a benign corpus, prioritization by actual exposure, that we apply in the ThreatClaw cloud native feed: cryptojacking detection rules arrive ready to deploy, already tested, so you do not have to redo that validation work yourself.

Related articles