Cornela
GitHub

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.

  • 0.1.7-alpha
  • Linux only
  • Rust userspace, C eBPF
  • Apache-2.0

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 --help
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.

system wide, into /usr/local/bin
curl -fsSL https://raw.githubusercontent.com/chud-lori/cornela/main/scripts/install-release.sh | sh
non root prefix
curl -fsSL https://raw.githubusercontent.com/chud-lori/cornela/main/scripts/install-release.sh | PREFIX="$HOME/.local" sh
from a downloaded archive
tar -xzf cornela-latest-x86_64-linux.tar.gz
cd cornela-*-linux
sudo ./install.sh
build from source (needed on aarch64)
git clone https://github.com/chud-lori/cornela.git
cd cornela
./scripts/install.sh

Environment variables the installer reads

VariableDefaultEffect
PREFIX/usr/local The binary is installed to $PREFIX/bin/cornela. The script uses sudo only when that directory is not writable.
CORNELA_VERSIONlatest 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_REPOchud-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 -s is not Linux.
  • 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 with scripts/install.sh: that needs cargo and clang to compile the eBPF object, and asks for sudo only for the final copy into $PREFIX/bin.
  • curl or wget.
  • sha256sum or shasum. 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.

Commands and their options, as parsed by the CLI.
CommandWhat it doesOptions
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

OptionEffect
--textHuman readable summary. The default.
--jsonThe same summary as a single JSON document.
--jsonlStreams 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.
--eventsCollects the individual events and lists them in the summary. Without it, the summary reports counts and sequence findings only.
--all-eventsTurns off the default filter and the runtime noise suppression, so every tracepoint hit is emitted.
--duration SECONDSStops after this many seconds. Without it the monitor runs until interrupted.
--max-events COUNTStops after this many events have been seen, counted before filtering.
--simulateFeeds four synthetic events through the correlation pipeline without loading eBPF. Runs anywhere, including on a non Linux host.

Build a monitor command

Pick the flags, then copy the line.

Output format
Behaviour
Bounds
sudo cornela monitor

    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 audit
    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 monitor --simulate --events
    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.

    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

    The type field on every event line.
    typeEmitted whenPasses the default filter
    af_alg_socketA socket is opened in the AF_ALG family.Always
    splicesplice() is called.Always
    process_execA process execs.Only when the binary's basename is a known setuid target
    privilege_transitionA setuid family syscall is called.Only when the detail carries target_uid=0
    group_transitionA setgid family syscall is called.Only when the detail carries target_gid=0
    namespace_changeunshare or setns is called.Always
    mount_attemptmount, move_mount, open_tree, or fsopen is called.Always
    bpf_attemptThe bpf syscall is called.Always
    capability_changecapset is called.Always
    module_loadinit_module, finit_module, or delete_module is called.Always
    keyring_accesskeyctl, 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

    Severities and reason strings, exactly as they are printed and serialised.
    RiskRaised whenreason
    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

    signalPresent means
    algif_aead_loadedThe algif_aead module appears in the loaded module list.
    af_alg_availableA kernel crypto API signal is present, read from /proc/crypto.
    seccomp_availableSeccomp support was detected. Its absence raises risk, because AF_ALG filtering may not be possible.
    containers_detectedAt least one container-like cgroup group was found, so kernel level escalation can become container escape risk.

    Status values

    statusMeaning
    mitigatedThe kernel version is at or above the published upstream fixed range.
    likelyalgif_aead is loaded and the crypto API signal is present.
    possibleRisk reached medium or above through some other signal.
    not_detectedNo signal raised the risk above low.
    unsupportedThe 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.