Node Agent Rule Library
Kubescape runtime threat detection can load CEL-based rules from the Rules custom resource (apiVersion: kubescape.io/v1, resource name rules.kubescape.io). When you install the operator with --set alertCRD.installDefault=true, the Helm chart creates a default-rules object from charts/kubescape-operator/templates/node-agent/default-rules.yaml.
The authoring source for these rules lives in kubescape/rulelibrary. Each rule lives under pkg/rules/r####-descriptive-name/, and ./gen.sh generates the combined rules-crd.yaml artifact.
Note
Treat the Helm chart template as the release snapshot and kubescape/rulelibrary as the authoring source. For cluster-specific changes, create your own Rules object instead of editing the chart template in place.
Rule families
Kubescape currently ships two broad rule families:
R0xxx: profile-driven anomaly rules that compare runtime activity to learned behavior.R1xxx: signature and behavioral rules that look for explicit suspicious activity.
The profileDependency field controls how much profile data a rule needs:
| Value | Meaning |
|---|---|
0 |
A profile is required. |
1 |
A profile is optional. |
2 |
A profile is not required. |
If you need a refresher on the learning phase and application profiles, see Runtime Threat Detection.
Default rules
In the current Helm chart template, the default rule library contains 26 rules: 22 enabled by default and 4 disabled by default (R0002, R0011, R1003, and R1011).
Profile-driven anomaly rules
| ID | Default | Event type | Rule | What it detects |
|---|---|---|---|---|
R0001 |
On | exec |
Unexpected process launched | Detects unexpected process launches that are not in the baseline. |
R0002 |
Off | open |
Files Access Anomalies in container | Detects unexpected file access that is not in the baseline. |
R0003 |
On | syscall |
Syscalls Anomalies in container | Detects unexpected system calls that are not whitelisted by the application profile. |
R0004 |
On | capabilities |
Linux Capabilities Anomalies in container | Detects unexpected capabilities that are not whitelisted by the application profile. |
R0005 |
On | dns |
DNS Anomalies in container | Detects unexpected domain requests that are not whitelisted by the learned profile. |
R0006 |
On | open |
Unexpected service account token access | Detects unexpected access to service account tokens. |
R0007 |
On | exec, network |
Workload uses Kubernetes API unexpectedly | Detects unplanned kubectl execution or traffic to the Kubernetes API server. |
R0008 |
On | open |
Read Environment Variables from procfs | Detects reading environment variables from procfs. |
R0009 |
On | bpf |
eBPF Program Load | Detects eBPF program load operations. |
R0010 |
On | open |
Unexpected Sensitive File Access | Detects unexpected access to sensitive files. |
R0011 |
Off | network |
Unexpected Egress Network Traffic | Detects unexpected outbound traffic that is not whitelisted by the learned profile. |
Signature and behavioral rules
| ID | Default | Event type | Rule | What it detects |
|---|---|---|---|---|
R1000 |
On | exec |
Process executed from malicious source | Detects execution from suspicious locations such as shared memory mounts. |
R1001 |
On | exec |
Drifted process executed | Detects execution of binaries that are not part of the base image. |
R1002 |
On | kmod |
Process tries to load a kernel module | Detects kernel module load attempts. |
R1003 |
Off | ssh |
Disallowed ssh connection | Detects SSH connections to disallowed ports. |
R1004 |
On | exec |
Process executed from mount | Detects execution from mounted paths. |
R1005 |
On | exec |
Fileless execution detected | Detects fileless execution techniques. |
R1006 |
On | unshare |
Process tries to escape container | Detects unshare usage that can be used for container escape. |
R1007 |
On | randomx |
Crypto miner launched | Detects XMRig-style cryptomining activity. |
R1008 |
On | dns |
Crypto Mining Domain Communication | Detects DNS communication with known cryptomining domains. |
R1009 |
On | network |
Crypto Mining Related Port Communication | Detects network traffic on suspicious cryptomining ports. |
R1010 |
On | symlink |
Soft link created over sensitive file | Detects symlink creation over sensitive files. |
R1011 |
Off | exec, open |
ld_preload hooks technique detected | Detects LD_PRELOAD-based hook techniques. |
R1012 |
On | hardlink |
Hard link created over sensitive file | Detects hardlink creation over sensitive files. |
R1015 |
On | ptrace |
Malicious Ptrace Usage | Detects potentially malicious ptrace usage. |
R1030 |
On | iouring |
Unexpected io_uring Operation Detected | Detects io_uring activity that was not observed during the learning period. |
For the full rule definitions, including severities, MITRE mappings, tags, and trigger settings, see:
CEL helpers and built-in functions
Kubescape rule expressions use standard CEL syntax plus several helper namespaces that the node-agent registers at runtime. For the base CEL operators, macros, and standard definitions, see the CEL language definition.
Kubescape also injects:
event: the current runtime event objecteventType: the current event type string, such asexecornetworkhttp: an alias toeventfor HTTP events
The implementation for the helper namespaces lives in the node-agent CEL libraries directory. The current ap.* and nn.* helpers are:
Application profile helpers (ap.*)
These helpers query learned workload behavior from the runtime profile cache. If the profile is not available yet, the helper resolves to false so rule evaluation can continue.
| Function | What it checks |
|---|---|
ap.was_executed(containerId, path) |
Whether the executable path was previously observed or declared in the Pod spec command/hooks. |
ap.was_executed_with_args(containerId, path, args) |
Whether the executable path and full argument list were previously observed. |
ap.was_path_opened(containerId, path) |
Whether the path was previously opened by the container. |
ap.was_path_opened_with_flags(containerId, path, flags) |
Whether the path was previously opened with the same open flags. |
ap.was_path_opened_with_suffix(containerId, suffix) |
Whether a previously opened path ends with the given suffix. |
ap.was_path_opened_with_prefix(containerId, prefix) |
Whether a previously opened path starts with the given prefix. |
ap.was_syscall_used(containerId, syscallName) |
Whether the syscall was recorded in the learned profile. |
ap.was_capability_used(containerId, capabilityName) |
Whether the Linux capability was previously observed. |
ap.was_endpoint_accessed(containerId, endpoint) |
Whether the HTTP endpoint path was previously observed. |
ap.was_endpoint_accessed_with_method(containerId, endpoint, method) |
Whether the HTTP endpoint was previously accessed with the given method. |
ap.was_endpoint_accessed_with_methods(containerId, endpoint, methods) |
Whether the HTTP endpoint was previously accessed with any method in the provided list. |
ap.was_endpoint_accessed_with_prefix(containerId, prefix) |
Whether any learned endpoint starts with the given prefix. |
ap.was_endpoint_accessed_with_suffix(containerId, suffix) |
Whether any learned endpoint ends with the given suffix. |
ap.was_host_accessed(containerId, host) |
Whether the host was previously observed in learned HTTP endpoint data. |
Network neighborhood helpers (nn.*)
These helpers query the learned ingress and egress addresses, domains, and port/protocol tuples associated with the workload profile.
| Function | What it checks |
|---|---|
nn.was_address_in_egress(containerId, address) |
Whether the IP address was seen in learned egress traffic. |
nn.was_address_in_ingress(containerId, address) |
Whether the IP address was seen in learned ingress traffic. |
nn.is_domain_in_egress(containerId, domain) |
Whether the domain was seen in learned egress DNS data. |
nn.is_domain_in_ingress(containerId, domain) |
Whether the domain was seen in learned ingress DNS data. |
nn.was_address_port_protocol_in_egress(containerId, address, port, protocol) |
Whether the address, port, and protocol tuple was seen in learned egress traffic. |
nn.was_address_port_protocol_in_ingress(containerId, address, port, protocol) |
Whether the address, port, and protocol tuple was seen in learned ingress traffic. |
Other helper namespaces
| Namespace | Functions | Purpose |
|---|---|---|
k8s.* |
get_container_mount_paths, is_api_server_address, get_container_by_name |
Query Pod-spec data and cluster API-server address information. |
parse.* |
get_exec_path |
Normalize exec arguments into an executable path. |
net.* |
is_private_ip |
Test whether an IP falls into private, loopback, or local-use ranges. |
process.* |
get_process_env, get_ld_hook_var |
Inspect process environment variables from /proc. |
Event field reference
The rule engine exposes the current event through event.*, and HTTP rules can also use the http.* alias. The authoritative field list is implemented in pkg/utils/cel.go.
Note
Only fields defined in utils.CelFields and utils.HttpRequestFields are available in CEL expressions. If a field exists in the underlying Go struct but is not in those maps, it is not available in rule expressions.
Common metadata fields
Most event types populate these top-level fields:
event.containerIdevent.containerNameevent.namespaceevent.podNameevent.commevent.pidevent.ppidevent.uid
Fields that are not populated by a given tracer evaluate to their empty or zero value, so it is best to guard rules with eventType and use the field subset that matches that event type.
Event-type-specific fields
| Event type | Commonly populated CEL fields |
|---|---|
exec |
event.args, event.cwd, event.exepath, event.pcomm, event.pupperlayer, event.upperlayer |
open |
event.path, event.flags, event.flagsRaw |
procfs |
event.path |
syscall |
event.syscallName |
capabilities |
event.capName, event.syscallName |
dns |
event.name, event.dstIp, event.dstPort, event.srcPort, event.proto, event.cwd, event.exepath |
network |
event.dstAddr, event.dstPort, event.pktType, event.proto |
ssh |
event.dstIp, event.dstPort, event.srcPort |
bpf |
event.cmd, event.attrSize, event.exepath, event.upperlayer |
kmod |
event.module, event.exepath, event.syscallName, event.upperlayer |
symlink / hardlink |
event.newPath, event.oldPath, event.exepath, event.upperlayer |
ptrace |
event.exepath |
unshare |
event.exepath, event.upperlayer |
iouring |
event.opcode, event.flags |
http |
event.direction, event.dstIp, event.dstPort, event.srcPort, event.request.headers, event.request.host, event.request.method, event.request.url, event.request.path, event.request.body |
randomx, fork, exit |
These event types currently rely on the common metadata set and do not add dedicated utils.CelFields entries beyond it. |
For the full event-type interfaces that back these fields, see pkg/utils/events.go.
Create your own rule
There are two common workflows:
- Create a cluster-local
Rulesobject for your own environment. - Contribute a reusable rule to
kubescape/rulelibrary.
1. Start from an existing rule
The fastest path is to copy a rule that already uses the event type you need and adjust the CEL expressions:
- Anomaly example:
r0001-unexpected-process-launched - Signature example:
r1000-exec-from-malicious-source
2. Create a Rules manifest
Create a namespaced Rules object with one or more rules under spec.rules. The CRD requires enabled, id, name, description, expressions, profileDependency, severity, supportPolicy, isTriggerAlert, mitreTactic, and mitreTechnique.
apiVersion: kubescape.io/v1
kind: Rules
metadata:
name: custom-runtime-rules
namespace: kubescape
spec:
rules:
- name: "Unexpected curl execution"
enabled: true
id: "R2000"
description: "Detects curl executions that were not part of the learned application profile."
expressions:
message: "'Unexpected curl execution: ' + event.comm"
uniqueId: "event.containerId + '_unexpected_curl'"
ruleExpression:
- eventType: "exec"
expression: "event.comm == 'curl' && !ap.was_executed(event.containerId, parse.get_exec_path(event.args, event.comm))"
profileDependency: 0
severity: 5
supportPolicy: false
isTriggerAlert: true
mitreTactic: "TA0002"
mitreTechnique: "T1059"
tags:
- "context:kubernetes"
- "custom"
- "exec"
- "applicationprofile"
Guidelines:
- Use an unused
R####identifier. - Keep the
messageanduniqueIdexpressions stable and deterministic. - If the rule depends on learned behavior, use
profileDependency: 0or1. - Reuse tags consistently so you can bind groups of rules with
ruleTags.
3. Bind the rule to workloads
RuntimeRuleAlertBinding controls where a rule is evaluated. You can target rules by ruleID, ruleName, or ruleTags.
apiVersion: kubescape.io/v1
kind: RuntimeRuleAlertBinding
metadata:
name: team-a-custom-rules
spec:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-a
rules:
- ruleID: "R2000"
Apply both manifests:
If your rule depends on learned behavior, give the workload time to finish the learning period first. You can shorten that period with nodeAgent.config.maxLearningPeriod; see Runtime Threat Detection.
4. Contribute upstream
If the rule should be part of the shared default library:
- Create a directory under
pkg/rules/using ther####-descriptive-name/convention. - Add the rule YAML file and a matching
rule_test.go. - Run
go test -v ./pkg/rules/.... - Run
./gen.shto regeneraterules-crd.yaml. - Open a pull request against
kubescape/rulelibrary.
This keeps the rule authoring workflow in one place and lets Helm pick up generated rule sets cleanly in later releases.