kntrl is an eBPF based runtime agent that monitors and prevents anomalous behaviour defined by you on your pipeline. kntrl achieves this by monitoring kernel calls, and denying access as soon as your defined behaviour is detected. For more: https://kntrl.dev
C
134
145 commits
updated Sep 17, 2026
kntrl is an eBPF-based runtime security agent that monitors and controls network, process, DNS, TLS, and file activity on CI/CD runners and build pipelines. It hooks into kernel-level syscalls to enforce policies in real time — blocking supply-chain attacks, unauthorized network access, and anomalous process behaviour before they cause damage.
Refer to this presentation for a deeper look at the architecture.
curl spawned by npm)./etc/shadow, /root/.ssh/).npm can only reach registry.npmjs.org)..rego files.monitor (log only) and trace (enforce and block)./proc/net/tcp{,6} at startup so existing SSH and database connections survive agent start.comm field (spoofable via prctl) with the real executable name from /proc/[pid]/exe."github.com" matches sub.github.com but NOT evil-github.com.AF_PACKET, IPPROTO_RAW, and IPPROTO_ICMP sockets.kntrl is available as a downloadable binary from the releases page. Download the pre-compiled binary and copy it to the desired location.
docker pull kondukto/kntrl:latest
To pull a specific version:
docker pull kondukto/kntrl:0.1.4
make generate # compile eBPF programs (requires clang, llvm, libelf-dev)
make build # build the Go binary to build/kntrl
Start the agent in monitor mode during your CI/CD job:
- name: start kntrl agent
run: sudo ./kntrl start --mode=monitor --allowed-hosts=download.kondukto.io,${{ env.GITHUB_ACTIONS_URL }} --allowed-ips=10.0.2.3 --daemonize
Stop and print the report:
- name: stop kntrl agent
run: sudo ./kntrl stop
Or with Docker:
- name: kntrl agent
run: sudo docker run --privileged \
--pid=host \
--network=host \
--cgroupns=host \
--volume=/sys/kernel/debug:/sys/kernel/debug:ro \
--volume /tmp:/tmp \
--rm docker.io/kondukto/kntrl:0.1.4 \
start --mode=trace --allowed-hosts=kondukto.io,download.kondukto.io
kntrl [command]
Commands:
start Start kntrl agent
stop Stop kntrl daemon and print reports
status Print kntrl daemon status
completion Generate shell autocompletion script
help Help about any command
Global flags:
-v, --verbose Enable verbose logging
--version Show version
kntrl start flags| Flag | Default | Description |
|---|---|---|
--mode | monitor | Operating mode: monitor (log only) or trace (enforce) |
--allowed-hosts | Comma-separated allowed hostnames (e.g., example.com,.github.com) | |
--allowed-ips | Comma-separated allowed IP addresses | |
--allow-local-ranges | true | Allow local IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) |
--allow-github-meta | false | Allow GitHub Actions IP ranges from api.github.com/meta |
--allow-metadata | false | Allow cloud metadata endpoints (169.254.169.254, 168.63.129.16) |
--monitor-processes | true | Enable process fork/exec monitoring |
--rules-file | Path to a YAML policy file | |
--rules-dir | Path to a directory containing .yaml and/or .rego rule files | |
-o, --output-file-name | /tmp/kntrl.out | Report output file |
--pretty | false | Pretty-print process events as a tree |
--daemonize | false | Run in the background |
Use --rules-file to load a comprehensive YAML policy. This gives fine-grained control over network access, process monitoring, DNS servers, file access, process ancestry chains, per-process profiles, and webhook alerting.
version: "1"
mode: trace
rules:
network:
allowed_hosts:
- "github.com"
- ".npmjs.org"
- ".amazonaws.com"
allowed_ips:
- "10.0.0.0/8"
- "172.16.0.0/12"
allow_local_ranges: true
allow_github_meta: true
allow_metadata: false
allowed_processes:
- "curl"
- "git"
- "wget"
- "docker"
profiles:
- process: "npm"
allowed_hosts:
- "registry.npmjs.org"
- process: "pip"
allowed_hosts:
- "pypi.org"
- "files.pythonhosted.org"
process:
enabled: true
blocked_chains:
- process: "curl"
ancestors: ["npm"]
- process: "wget"
ancestors: ["pip"]
dns:
allowed_servers:
- "8.8.8.8"
- "8.8.4.4"
file:
enabled: false
monitored_paths:
- "/etc/shadow"
- "/root/.ssh/"
- "/proc/self/environ"
webhooks:
- url: "https://your-siem.example.com/events"
headers:
Authorization: "Bearer <token>"
filter: "block"
See examples/policy-v2.yaml for a full example.
Send SIGHUP to the running agent to reload the YAML rules file and flush policy caches without restarting:
kill -HUP $(cat /tmp/kntrl.pid)
kntrl tracks process fork/exec events to build an in-memory process tree. When a network connection is made, kntrl walks the tree to determine the full ancestry chain of the connecting process. This ancestry is then evaluated against blocked_chains rules.
Each blocked_chains entry specifies a process name and a list of ancestors. If the process making the network connection matches process and every name in ancestors appears somewhere in its ancestry chain, the connection is denied.
This lets you write rules like "block curl if it was spawned (directly or indirectly) by npm":
rules:
process:
enabled: true
blocked_chains:
- process: "curl"
ancestors: ["npm"]
With this rule:
npm install spawning sh -> curl to exfiltrate data is blockedcurl github.com directly from a shell is allowedYou can require multiple ancestors to be present:
blocked_chains:
- process: "sh"
ancestors: ["npm", "node"]
The ancestry chain is also passed into OPA policy evaluation as input.ancestors, so you can write custom Rego rules against it.
Restrict which hosts a specific process can reach. For example, allow npm to reach only its registry:
rules:
network:
profiles:
- process: "npm"
allowed_hosts:
- "registry.npmjs.org"
- process: "pip"
allowed_hosts:
- "pypi.org"
- "files.pythonhosted.org"
If the connecting process matches a profile, only the hosts listed in that profile are allowed — the global allowed list does not apply.
kntrl captures DNS queries and responses at the kernel level via eBPF kprobes. This provides:
rules:
dns:
allowed_servers:
- "8.8.8.8"
- "8.8.4.4"
DNS events are displayed in the report as a separate table:
Domain | DNS Server
------------------------------------------
registry.npmjs.org | 8.8.8.8
github.com | 8.8.8.8
kntrl uses eBPF TC (traffic control) hooks to inspect TLS ClientHello packets and extract the Server Name Indication (SNI) field. This allows domain-based policy enforcement even for encrypted connections, correlating the SNI with the network connection event.
Track access to sensitive files:
rules:
file:
enabled: true
monitored_paths:
- "/etc/shadow"
- "/root/.ssh/"
- "/proc/self/environ"
File events appear in a separate report table:
Pid | Comm | Filename | Flags
---------------------------------------------------
1234 | python | /etc/shadow | 0
Send events to external systems (SIEM, Slack, etc.) in real time:
webhooks:
- url: "https://your-siem.example.com/events"
headers:
Authorization: "Bearer <token>"
filter: "block" # "block", "pass", or "all"
Events are delivered asynchronously via buffered channels with a 5-second HTTP timeout per webhook.
kntrl uses an embedded OPA policy engine. All policy rules live under bundle/kntrl/ and are compiled into the binary. The policy evaluation flow:
pass or block| Policy | Description |
|---|---|
is_allowed_hosts | Match domains against allowed host list with dot-boundary safety |
is_allowed_ip | Match IPs against explicit allowlist |
is_allowed_cidr | Match IPs against CIDR ranges |
is_local_ip_addr | Allow RFC 1918 private ranges |
is_github_range | Allow GitHub Actions infrastructure IPs |
is_metadata | Control cloud metadata endpoint access |
is_process_profile | Per-process allowed hosts |
ancestry | Block based on process ancestry chains |
custom | Placeholder for user-defined rules |
Add custom .rego files via --rules-dir:
package kntrl.network["my_custom_rule"]
import rego.v1
policy if {
input.task_name == "curl"
some ancestor in input.ancestors
ancestor == "npm"
}
make test-rego-local # requires opa CLI
make test-rego # runs in Docker
Events are logged to the output file (default /tmp/kntrl.out) in JSON format, one event per line:
{
"pid": 2806,
"task_name": "curl",
"proto": "tcp",
"daddr": "140.82.114.22",
"dport": 443,
"domains": ["lb-140-82-114-22-iad.github.com"],
"policy": "pass",
"ancestors": ["bash"]
}
{
"pid": 3201,
"task_name": "curl",
"proto": "tcp",
"daddr": "evil.example.com",
"dport": 443,
"domains": ["evil.example.com"],
"policy": "block",
"ancestors": ["sh", "npm", "node", "bash"]
}
When the agent stops, summary tables are printed:
Pid | Comm | Proto | Domain | Destination Addr | Policy
---------------------------------------------------------------------------------------
2806 | curl | tcp | lb-140-82-114-22-iad.github.com | 140.82.114.22:443 | pass
---------------------------------------------------------------------------------------
3201 | curl | tcp | evil.example.com | 93.184.216.34:443 | block
Use --pretty to display process events as a tree:
Process Tree
[1234] /bin/bash
├── [1235] npm install
│ └── [1236] sh -c curl http://evil.com
│ └── [1237] curl http://evil.com
└── [1240] git clone https://github.com/org/repo
kntrl attaches the following eBPF programs to kernel hooks:
| Hook | Type | Purpose |
|---|---|---|
tcp_v4_connect | kprobe | IPv4 TCP connection attempts |
ip4_datagram_connect | kprobe | IPv4 UDP connection attempts |
tcp_v6_connect | kprobe | IPv6 TCP connection attempts |
udpv6_sendmsg | kprobe | IPv6 UDP sends |
skb_consume_udp | kprobe | DNS packet capture |
inet_sock_set_state | tracepoint | TCP state change tracking |
sched_process_exec | tracepoint | Process execution events |
sched_process_fork | tracepoint | Process fork events |
security_socket_create | kprobe | Raw socket creation detection |
| TC classifier | tc | TLS SNI extraction from packets |
All event data flows through eBPF ring buffers for efficient kernel-to-userspace communication. BPF maps use LRU eviction to prevent overflow under high load.
make test-unit # unit tests (Docker)
make test-rego # OPA policy tests (Docker)
make test-ebpf # eBPF tests (requires Linux kernel)
make test-integration # integration tests (requires Linux kernel)
make test-all # all tests (Docker)
make test-unit-local # unit tests (local, works on macOS)
make test-rego-local # OPA policy tests (local, requires opa CLI)
Contributions to kntrl are welcome. Feel free to join our Slack channel https://kntrl.slack.com
Except for the eBPF code, all components are distributed under the Apache License (version 2.0).
kntrl is an open source project maintained by Kondukto.
C
93.7%
Go
5.8%
kntrl is an eBPF based runtime agent that monitors and prevents anomalous behaviour defined by you on your pipeline. kntrl achieves this by monitoring kernel calls, and denying access as soon as your defined behaviour is detected. For more: https://kntrl.dev
C
134
145 commits
updated Sep 17, 2026
kntrl is an eBPF-based runtime security agent that monitors and controls network, process, DNS, TLS, and file activity on CI/CD runners and build pipelines. It hooks into kernel-level syscalls to enforce policies in real time — blocking supply-chain attacks, unauthorized network access, and anomalous process behaviour before they cause damage.
Refer to this presentation for a deeper look at the architecture.
curl spawned by npm)./etc/shadow, /root/.ssh/).npm can only reach registry.npmjs.org)..rego files.monitor (log only) and trace (enforce and block)./proc/net/tcp{,6} at startup so existing SSH and database connections survive agent start.comm field (spoofable via prctl) with the real executable name from /proc/[pid]/exe."github.com" matches sub.github.com but NOT evil-github.com.AF_PACKET, IPPROTO_RAW, and IPPROTO_ICMP sockets.kntrl is available as a downloadable binary from the releases page. Download the pre-compiled binary and copy it to the desired location.
docker pull kondukto/kntrl:latest
To pull a specific version:
docker pull kondukto/kntrl:0.1.4
make generate # compile eBPF programs (requires clang, llvm, libelf-dev)
make build # build the Go binary to build/kntrl
Start the agent in monitor mode during your CI/CD job:
- name: start kntrl agent
run: sudo ./kntrl start --mode=monitor --allowed-hosts=download.kondukto.io,${{ env.GITHUB_ACTIONS_URL }} --allowed-ips=10.0.2.3 --daemonize
Stop and print the report:
- name: stop kntrl agent
run: sudo ./kntrl stop
Or with Docker:
- name: kntrl agent
run: sudo docker run --privileged \
--pid=host \
--network=host \
--cgroupns=host \
--volume=/sys/kernel/debug:/sys/kernel/debug:ro \
--volume /tmp:/tmp \
--rm docker.io/kondukto/kntrl:0.1.4 \
start --mode=trace --allowed-hosts=kondukto.io,download.kondukto.io
kntrl [command]
Commands:
start Start kntrl agent
stop Stop kntrl daemon and print reports
status Print kntrl daemon status
completion Generate shell autocompletion script
help Help about any command
Global flags:
-v, --verbose Enable verbose logging
--version Show version
kntrl start flags| Flag | Default | Description |
|---|---|---|
--mode | monitor | Operating mode: monitor (log only) or trace (enforce) |
--allowed-hosts | Comma-separated allowed hostnames (e.g., example.com,.github.com) | |
--allowed-ips | Comma-separated allowed IP addresses | |
--allow-local-ranges | true | Allow local IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) |
--allow-github-meta | false | Allow GitHub Actions IP ranges from api.github.com/meta |
--allow-metadata | false | Allow cloud metadata endpoints (169.254.169.254, 168.63.129.16) |
--monitor-processes | true | Enable process fork/exec monitoring |
--rules-file | Path to a YAML policy file | |
--rules-dir | Path to a directory containing .yaml and/or .rego rule files | |
-o, --output-file-name | /tmp/kntrl.out | Report output file |
--pretty | false | Pretty-print process events as a tree |
--daemonize | false | Run in the background |
Use --rules-file to load a comprehensive YAML policy. This gives fine-grained control over network access, process monitoring, DNS servers, file access, process ancestry chains, per-process profiles, and webhook alerting.
version: "1"
mode: trace
rules:
network:
allowed_hosts:
- "github.com"
- ".npmjs.org"
- ".amazonaws.com"
allowed_ips:
- "10.0.0.0/8"
- "172.16.0.0/12"
allow_local_ranges: true
allow_github_meta: true
allow_metadata: false
allowed_processes:
- "curl"
- "git"
- "wget"
- "docker"
profiles:
- process: "npm"
allowed_hosts:
- "registry.npmjs.org"
- process: "pip"
allowed_hosts:
- "pypi.org"
- "files.pythonhosted.org"
process:
enabled: true
blocked_chains:
- process: "curl"
ancestors: ["npm"]
- process: "wget"
ancestors: ["pip"]
dns:
allowed_servers:
- "8.8.8.8"
- "8.8.4.4"
file:
enabled: false
monitored_paths:
- "/etc/shadow"
- "/root/.ssh/"
- "/proc/self/environ"
webhooks:
- url: "https://your-siem.example.com/events"
headers:
Authorization: "Bearer <token>"
filter: "block"
See examples/policy-v2.yaml for a full example.
Send SIGHUP to the running agent to reload the YAML rules file and flush policy caches without restarting:
kill -HUP $(cat /tmp/kntrl.pid)
kntrl tracks process fork/exec events to build an in-memory process tree. When a network connection is made, kntrl walks the tree to determine the full ancestry chain of the connecting process. This ancestry is then evaluated against blocked_chains rules.
Each blocked_chains entry specifies a process name and a list of ancestors. If the process making the network connection matches process and every name in ancestors appears somewhere in its ancestry chain, the connection is denied.
This lets you write rules like "block curl if it was spawned (directly or indirectly) by npm":
rules:
process:
enabled: true
blocked_chains:
- process: "curl"
ancestors: ["npm"]
With this rule:
npm install spawning sh -> curl to exfiltrate data is blockedcurl github.com directly from a shell is allowedYou can require multiple ancestors to be present:
blocked_chains:
- process: "sh"
ancestors: ["npm", "node"]
The ancestry chain is also passed into OPA policy evaluation as input.ancestors, so you can write custom Rego rules against it.
Restrict which hosts a specific process can reach. For example, allow npm to reach only its registry:
rules:
network:
profiles:
- process: "npm"
allowed_hosts:
- "registry.npmjs.org"
- process: "pip"
allowed_hosts:
- "pypi.org"
- "files.pythonhosted.org"
If the connecting process matches a profile, only the hosts listed in that profile are allowed — the global allowed list does not apply.
kntrl captures DNS queries and responses at the kernel level via eBPF kprobes. This provides:
rules:
dns:
allowed_servers:
- "8.8.8.8"
- "8.8.4.4"
DNS events are displayed in the report as a separate table:
Domain | DNS Server
------------------------------------------
registry.npmjs.org | 8.8.8.8
github.com | 8.8.8.8
kntrl uses eBPF TC (traffic control) hooks to inspect TLS ClientHello packets and extract the Server Name Indication (SNI) field. This allows domain-based policy enforcement even for encrypted connections, correlating the SNI with the network connection event.
Track access to sensitive files:
rules:
file:
enabled: true
monitored_paths:
- "/etc/shadow"
- "/root/.ssh/"
- "/proc/self/environ"
File events appear in a separate report table:
Pid | Comm | Filename | Flags
---------------------------------------------------
1234 | python | /etc/shadow | 0
Send events to external systems (SIEM, Slack, etc.) in real time:
webhooks:
- url: "https://your-siem.example.com/events"
headers:
Authorization: "Bearer <token>"
filter: "block" # "block", "pass", or "all"
Events are delivered asynchronously via buffered channels with a 5-second HTTP timeout per webhook.
kntrl uses an embedded OPA policy engine. All policy rules live under bundle/kntrl/ and are compiled into the binary. The policy evaluation flow:
pass or block| Policy | Description |
|---|---|
is_allowed_hosts | Match domains against allowed host list with dot-boundary safety |
is_allowed_ip | Match IPs against explicit allowlist |
is_allowed_cidr | Match IPs against CIDR ranges |
is_local_ip_addr | Allow RFC 1918 private ranges |
is_github_range | Allow GitHub Actions infrastructure IPs |
is_metadata | Control cloud metadata endpoint access |
is_process_profile | Per-process allowed hosts |
ancestry | Block based on process ancestry chains |
custom | Placeholder for user-defined rules |
Add custom .rego files via --rules-dir:
package kntrl.network["my_custom_rule"]
import rego.v1
policy if {
input.task_name == "curl"
some ancestor in input.ancestors
ancestor == "npm"
}
make test-rego-local # requires opa CLI
make test-rego # runs in Docker
Events are logged to the output file (default /tmp/kntrl.out) in JSON format, one event per line:
{
"pid": 2806,
"task_name": "curl",
"proto": "tcp",
"daddr": "140.82.114.22",
"dport": 443,
"domains": ["lb-140-82-114-22-iad.github.com"],
"policy": "pass",
"ancestors": ["bash"]
}
{
"pid": 3201,
"task_name": "curl",
"proto": "tcp",
"daddr": "evil.example.com",
"dport": 443,
"domains": ["evil.example.com"],
"policy": "block",
"ancestors": ["sh", "npm", "node", "bash"]
}
When the agent stops, summary tables are printed:
Pid | Comm | Proto | Domain | Destination Addr | Policy
---------------------------------------------------------------------------------------
2806 | curl | tcp | lb-140-82-114-22-iad.github.com | 140.82.114.22:443 | pass
---------------------------------------------------------------------------------------
3201 | curl | tcp | evil.example.com | 93.184.216.34:443 | block
Use --pretty to display process events as a tree:
Process Tree
[1234] /bin/bash
├── [1235] npm install
│ └── [1236] sh -c curl http://evil.com
│ └── [1237] curl http://evil.com
└── [1240] git clone https://github.com/org/repo
kntrl attaches the following eBPF programs to kernel hooks:
| Hook | Type | Purpose |
|---|---|---|
tcp_v4_connect | kprobe | IPv4 TCP connection attempts |
ip4_datagram_connect | kprobe | IPv4 UDP connection attempts |
tcp_v6_connect | kprobe | IPv6 TCP connection attempts |
udpv6_sendmsg | kprobe | IPv6 UDP sends |
skb_consume_udp | kprobe | DNS packet capture |
inet_sock_set_state | tracepoint | TCP state change tracking |
sched_process_exec | tracepoint | Process execution events |
sched_process_fork | tracepoint | Process fork events |
security_socket_create | kprobe | Raw socket creation detection |
| TC classifier | tc | TLS SNI extraction from packets |
All event data flows through eBPF ring buffers for efficient kernel-to-userspace communication. BPF maps use LRU eviction to prevent overflow under high load.
make test-unit # unit tests (Docker)
make test-rego # OPA policy tests (Docker)
make test-ebpf # eBPF tests (requires Linux kernel)
make test-integration # integration tests (requires Linux kernel)
make test-all # all tests (Docker)
make test-unit-local # unit tests (local, works on macOS)
make test-rego-local # OPA policy tests (local, requires opa CLI)
Contributions to kntrl are welcome. Feel free to join our Slack channel https://kntrl.slack.com
Except for the eBPF code, all components are distributed under the Apache License (version 2.0).
kntrl is an open source project maintained by Kondukto.
C
93.7%
Go
5.8%