Audit a Linux container host for shared-kernel escape risk
Cornela is a command line auditor. It reads host hardening state, groups
container-like processes out of /proc cgroups, profiles kernel
exposure for CVE-2026-31431 (Copy Fail), and correlates live syscall
sequences through eBPF. This page is its reference documentation.
Container security state is scattered across kernel version, loaded modules, cgroups, namespaces, capabilities, seccomp, LSM status, runtime metadata, and live syscall behaviour. Cornela reads those places and prints findings with a risk level, a reason, and the process or container the finding came from.
It correlates rather than alerts on single syscalls. A process that
opens an AF_ALG socket and calls splice()
inside the same 30 second window produces one finding; add a UID
transition to root inside that window and the severity rises.
What it does not do
- It does not exploit vulnerabilities and does not run proof of concept exploit code.
- It does not modify containers or the host.
- It does not replace kernel patching or container hardening.
- A finding does not prove exploitation. It says where to look.
Status is alpha: ready for public testing and defensive lab use, not yet a mature production security product.
Cornela - Container Kernel Auditor for eBPF-based escape risk detection Usage: cornela audit [--json] cornela containers [--json] cornela cve CVE-2026-31431 [--json] cornela report [--output PATH|--stdout] cornela monitor [--json|--jsonl] [--events] [--all-events] [--duration SECONDS] [--max-events COUNT] [--simulate] Commands: audit Audit host hardening and detected container risk signals containers List container-like process groups discovered from /proc cgroups cve Run a defensive CVE exposure profile report Write a JSON audit report monitor Run the eBPF monitor or simulate the detection pipeline Monitor output defaults to interesting AF_ALG, splice, root UID transition, and setuid-target exec events. Use --all-events for raw tracepoint noise.
Printed verbatim by cornela --help, and by
cornela with no arguments.
Install
Installing from a published release needs no Rust, Cargo, Git, or source checkout. The installer refuses to unpack an archive whose SHA-256 does not match the published checksum file.
curl -fsSL https://raw.githubusercontent.com/chud-lori/cornela/main/scripts/install-release.sh | sh
curl -fsSL https://raw.githubusercontent.com/chud-lori/cornela/main/scripts/install-release.sh | PREFIX="$HOME/.local" sh
tar -xzf cornela-latest-x86_64-linux.tar.gz cd cornela-*-linux sudo ./install.sh
git clone https://github.com/chud-lori/cornela.git cd cornela ./scripts/install.sh
Environment variables the installer reads
| Variable | Default | Effect |
|---|---|---|
| PREFIX | /usr/local | The binary is installed to $PREFIX/bin/cornela. The script uses sudo only when that directory is not writable. |
| CORNELA_VERSION | latest | A release tag. With latest the installer fetches the cornela-latest-* assets. With a tag it strips the leading v and asks for cornela-<version>-<arch>-linux.tar.gz under that tag, so the tag only resolves when its assets carry the matching version in their file names. Check the releases page before pinning. |
| CORNELA_REPO | chud-lori/cornela | Where release assets are fetched from. Useful when mirroring releases internally. |
What the host needs
To install
- Linux. The installer exits if
uname -sis notLinux. - x86_64. The installer also recognises
aarch64, but releases are built on an x86_64 runner, so no aarch64 archive is published yet. On aarch64, build from source withscripts/install.sh: that needscargoandclangto compile the eBPF object, and asks forsudoonly for the final copy into$PREFIX/bin. curlorwget.sha256sumorshasum. The installer stops rather than install an unverified archive.
To run the live monitor
- root, or capabilities sufficient to load BPF programs.
- Kernel support for BPF ring buffers.
- Ten required tracepoints: socket enter and exit,
splice, process exec, and the setuid and setgid families. The monitor stops if any of them fails to attach. - Fifteen optional tracepoints: namespace, mount, BPF, capability, module, keyring, and process exit. Each is skipped on its own when the kernel does not offer it.
On macOS, Docker Desktop runs containers inside a Linux VM. Run
Cornela inside that VM or on the real Linux host, not on the macOS
side, where the audit reports Linux audit support: no.
Command reference
Five commands plus help. cornela with no arguments,
cornela help, --help, and -h all
print the usage text. --help is also honoured on
report and monitor. On audit,
containers, and cve it is consumed as an output
mode token instead, so those commands run normally in text mode rather
than printing usage. An unknown command or option prints
cornela: <reason> on stderr and exits 1.
| Command | What it does | Options |
|---|---|---|
| audit | Host hardening plus container risk summary, with reasons, recommendations, and every CVE profile. | --json, --text |
| containers | Container-like process groups discovered from /proc cgroups, with namespaces, capabilities, seccomp, NoNewPrivs, and mount state. |
--json, --text |
| cve <ID> | A defensive CVE exposure profile. CVE-2026-31431 is the only recognised ID; anything else exits 1. |
--json, --text |
| report | Writes the same JSON document the audit produces to a file. | --output PATH, -o PATH, --stdout |
| monitor | Loads the eBPF probes and correlates live events, or runs the pipeline against synthetic events. | see below |
| help | Prints usage. Also what runs when Cornela is called with no arguments at all. | --help, -h |
report writes to cornela-report.json in the
working directory unless --output names another path, and
prints wrote <path> when it finishes.
--stdout prints the document instead of writing a file.
Monitor options
| Option | Effect |
|---|---|
| --text | Human readable summary. The default. |
| --json | The same summary as a single JSON document. |
| --jsonl | Streams one JSON object per line as events and findings occur. Suppresses the end of run summary entirely, so --json and --text have no effect alongside it. |
| --events | Collects the individual events and lists them in the summary. Without it, the summary reports counts and sequence findings only. |
| --all-events | Turns off the default filter and the runtime noise suppression, so every tracepoint hit is emitted. |
| --duration SECONDS | Stops after this many seconds. Without it the monitor runs until interrupted. |
| --max-events COUNT | Stops after this many events have been seen, counted before filtering. |
| --simulate | Feeds four synthetic events through the correlation pipeline without loading eBPF. Runs anywhere, including on a non Linux host. |
Output
Every block below was captured from Cornela 0.1.7-alpha built from this repository. The capture host is macOS, so the Linux specific checks report that they could not run. That is the tool's real answer on a non Linux host, not a placeholder.
Cornela Host Audit Risk: medium Report schema: 1 Generated at: 1789214287 OS: macos Linux audit support: no Kernel: 24.6.0 algif_aead: no AF_ALG signal: no seccomp: no AppArmor: no SELinux: no user namespaces: unknown container runtimes: docker, kubectl containers detected: 0 container risk summary: low=0, medium=0, high=0, critical=0 Reasons: - Cornela host audit is designed for Linux; current OS is macos Recommendations: - Run Cornela on the Linux container host or VM for kernel-level audit results. CVE Profiles: - CVE-2026-31431 Copy Fail: status=unsupported, risk=medium
On a terminal, risk levels and yes or no values are coloured. Set
NO_COLOR, or redirect to a file, and the escape codes are
dropped.
Cornela Runtime Monitor OS: macos Linux support: no eBPF loader ready: no event enrichment ready: yes sequence tracking ready: yes sequence window: 30s planned probes: tracepoint/syscalls/sys_enter_socket, tracepoint/syscalls/sys_exit_socket, ... Reasons: - simulation mode generated synthetic AF_ALG and splice events simulation: yes events seen: 4 events emitted: 4 Events: - type=af_alg_socket pid=4242 uid=unknown comm=python3 container=simulated-container syscall=socket detail=family=AF_ALG - type=splice pid=4242 uid=unknown comm=python3 container=simulated-container syscall=splice detail=splice called - type=privilege_transition pid=4242 uid=unknown comm=python3 container=simulated-container syscall=setuid detail=target_uid=0 - type=bpf_attempt pid=4242 uid=unknown comm=python3 container=simulated-container syscall=bpf detail=bpf command=5 Sequence Findings: - risk=high pid=4242 container=simulated-container reason=process used AF_ALG and splice within the Copy Fail correlation window - risk=critical pid=4242 container=simulated-container reason=process used AF_ALG and splice before a UID transition to root within the Copy Fail correlation window - risk=critical pid=4242 container=simulated-container reason=process attempted to use the bpf syscall
The probe line is shortened here: the real line lists all 25 planned
probes on one line. They are itemised under
Detection. Everything else is verbatim.
simulation: yes and the simulated-container
identifier are Cornela's own labels for synthetic events, printed by the
tool, not added for this page.
{
"id": "CVE-2026-31431",
"name": "Copy Fail",
"status": "unsupported",
"risk": "medium",
"kernel": {"version":"24.6.0","parsed":null,"fixed_by_upstream_version":null,"note":"not assessed because the current host is not Linux"},
"signals": [],
"reasons": [
"CVE-2026-31431 can only be assessed on the Linux kernel host"
],
"recommendations": [
"Run this profile on the Linux host or VM that actually runs the containers."
]
}
cornela report --stdout prints a larger document with the
same shape: tool, metadata (schema version 1
and a Unix timestamp), risk, summary counts,
host, containers, cve_profiles,
reasons, and recommendations.
{"type":"af_alg_socket","severity":"high","pid":4242,"ppid":null,"uid":null,"gid":null,"comm":"python3","command_line":null,"container_id":"simulated-container","cgroup_path":"/docker/simulated-container","pid_namespace":null,"mount_namespace":null,"network_namespace":null,"syscall":"socket","detail":"family=AF_ALG","timestamp_ns":1000}
{"type":"splice","severity":"high","pid":4242,...,"syscall":"splice","detail":"splice called","timestamp_ns":2000}
{"severity":"high","pid":4242,"container_id":"simulated-container","first_timestamp_ns":1000,"last_timestamp_ns":2000,"event_types":["af_alg_socket","splice"],"reason":"process used AF_ALG and splice within the Copy Fail correlation window"}
{"type":"privilege_transition","severity":"high","pid":4242,...,"syscall":"setuid","detail":"target_uid=0","timestamp_ns":3000}
{"severity":"critical","pid":4242,"container_id":"simulated-container","first_timestamp_ns":1000,"last_timestamp_ns":3000,"event_types":["af_alg_socket","splice","privilege_transition"],"reason":"process used AF_ALG and splice before a UID transition to root within the Copy Fail correlation window"}
Lines two, four and five are abbreviated at the marked point so the
block stays readable; each carries the same keys as line one. Two record
shapes share the stream: an event line has type and
comm, a finding line has event_types and
reason. Both carry severity, pid,
and container_id, so a consumer can branch on whether
type is present.
The states you will meet that are not a normal finding list.
No container-like cgroups detected.
Not an error. On a Linux host it means no cgroup path matched a
container-like pattern. On any other OS it is the only possible answer,
because discovery reads /proc.
$ cornela cve CVE-2021-4034 cornela: unsupported CVE profile: CVE-2021-4034
$ cornela audit --yaml cornela: unknown option: --yaml
Errors go to stderr with a cornela: prefix and exit 1.
Everything else exits 0, including a run that found nothing.
Reasons: - runtime eBPF monitoring requires Linux; current OS is macos - preflight only; eBPF loader has not attached probes in this mode simulation: no events seen: 0 events emitted: 0
The monitor exits 0 here. It reports what it could and could not do
rather than failing. On a Linux host missing
/sys/kernel/btf/vmlinux, the reasons list gains a line
about CO-RE portability being limited.
Detection reference
Probes emit events. Events are enriched with process and container context. A per process, per container state machine holds them for 30 seconds and emits a finding when a sequence completes.
Event types
| type | Emitted when | Passes the default filter |
|---|---|---|
| af_alg_socket | A socket is opened in the AF_ALG family. | Always |
| splice | splice() is called. | Always |
| process_exec | A process execs. | Only when the binary's basename is a known setuid target |
| privilege_transition | A setuid family syscall is called. | Only when the detail carries target_uid=0 |
| group_transition | A setgid family syscall is called. | Only when the detail carries target_gid=0 |
| namespace_change | unshare or setns is called. | Always |
| mount_attempt | mount, move_mount, open_tree, or fsopen is called. | Always |
| bpf_attempt | The bpf syscall is called. | Always |
| capability_change | capset is called. | Always |
| module_load | init_module, finit_module, or delete_module is called. | Always |
| keyring_access | keyctl, add_key, or request_key is called. | Always |
The default filter also drops two kinds of noise that would otherwise pass:
anything whose comm is cornela itself, and
namespace, mount, capability, keyring, UID, and GID events coming from
runc, containerd, dockerd,
containerd-shim, or docker-init, which is what
normal container startup looks like. --all-events turns both
the filter and the suppression off.
Sequence findings
| Risk | Raised when | reason |
|---|---|---|
| high | af_alg_socket and splice from one process inside the window. |
process used AF_ALG and splice within the Copy Fail correlation window |
| critical | The pair above, plus a UID transition to root inside the same window. | process used AF_ALG and splice before a UID transition to root within the Copy Fail correlation window |
| high | namespace_change and mount_attempt inside the window. |
process changed namespace context and attempted mount-related activity within the correlation window |
| critical | A bpf_attempt event. Reported once per process. |
process attempted to use the bpf syscall |
| critical | A module_load event. Reported once per process. |
process attempted kernel module load or unload activity |
| high | A capability_change event. Reported once per process. |
process attempted to change Linux capability sets |
| medium | A keyring_access event. Reported once per process. |
process used Linux keyring syscalls |
State is keyed by process ID and container ID together, and each sequence is reported once per key, so a busy process does not repeat the same finding. The window is 30 seconds.
The 25 tracepoints the monitor attaches
Required: 10
If any one of these fails to attach, the monitor stops and reports the failure. UID and GID transitions are both in this group.
- tracepoint/syscalls/sys_enter_socket
- tracepoint/syscalls/sys_exit_socket
- tracepoint/syscalls/sys_enter_splice
- tracepoint/sched/sched_process_exec
- tracepoint/syscalls/sys_enter_setuid
- tracepoint/syscalls/sys_enter_setreuid
- tracepoint/syscalls/sys_enter_setresuid
- tracepoint/syscalls/sys_enter_setgid
- tracepoint/syscalls/sys_enter_setregid
- tracepoint/syscalls/sys_enter_setresgid
Optional: 15
Attached individually where the kernel offers them, skipped where it does not. Losing one costs the events and findings that depend on it, not the run.
- tracepoint/syscalls/sys_enter_unshare
- tracepoint/syscalls/sys_enter_setns
- tracepoint/syscalls/sys_enter_mount
- tracepoint/syscalls/sys_enter_move_mount
- tracepoint/syscalls/sys_enter_open_tree
- tracepoint/syscalls/sys_enter_fsopen
- tracepoint/syscalls/sys_enter_bpf
- tracepoint/syscalls/sys_enter_capset
- tracepoint/syscalls/sys_enter_init_module
- tracepoint/syscalls/sys_enter_finit_module
- tracepoint/syscalls/sys_enter_delete_module
- tracepoint/syscalls/sys_enter_keyctl
- tracepoint/syscalls/sys_enter_add_key
- tracepoint/syscalls/sys_enter_request_key
- tracepoint/sched/sched_process_exit
The Copy Fail profile
cornela cve CVE-2026-31431 is exposure triage, not a verdict.
It reads four signals off the host, maps the kernel version against a
published fixed range heuristic, and returns a status.
Signals it reads
| signal | Present means |
|---|---|
| algif_aead_loaded | The algif_aead module appears in the loaded module list. |
| af_alg_available | A kernel crypto API signal is present, read from /proc/crypto. |
| seccomp_available | Seccomp support was detected. Its absence raises risk, because AF_ALG filtering may not be possible. |
| containers_detected | At least one container-like cgroup group was found, so kernel level escalation can become container escape risk. |
Status values
| status | Meaning |
|---|---|
| mitigated | The kernel version is at or above the published upstream fixed range. |
| likely | algif_aead is loaded and the crypto API signal is present. |
| possible | Risk reached medium or above through some other signal. |
| not_detected | No signal raised the risk above low. |
| unsupported | The host is not Linux, so nothing could be assessed. |
What it tells you to do
Unless the status is mitigated, the profile returns these
recommendations, sorted and deduplicated:
- Patch the Linux kernel to a vendor-fixed build.
- Temporarily disable or block algif_aead and AF_ALG AEAD access where operationally safe.
- Use seccomp to block unnecessary AF_ALG socket creation in untrusted containers.
- Treat this result as exposure triage: confirm patched status with vendor advisories and package metadata.
The repository ships a harmless demo script that generates the AF_ALG and
splice signals so you can confirm the monitor sees them. It does not
exploit the CVE, does not attempt escape, and does not modify host files.
Run sudo cornela monitor --events --duration 30 in one
terminal and python3 scripts/demo_copy_fail_signals.py in
another. The walkthrough is in
the Copy Fail demo guide.