Runtime security, through a different lens.
Observe execution. Inspect its origin. Understand the anomaly.
Quickstart / Real demo / Design / Threat model / Contribute
Concept artwork, not telemetry. Still version · Art direction & motion
A small Rust runtime sensor that inspects where Linux syscalls come from.
Injected code can change its bytes. It still has to execute somewhere. Wraith uses
ptrace and /proc/<pid>/maps to flag sensitive syscalls from suspicious executable
memory, inspect W^X requests, and correlate signals across threads. No payload signatures,
cloud service, or model required. Only two direct Rust dependencies: nix and libc.
Continuously developed, not a finalized endpoint product. The focus is reproducible Linux security investigation with honest coverage and workload evidence—not an antivirus or Nmap replacement. See the development program and Linux/Kali tool comparison.
| Inspect the origin Executable mappings and syscall provenance. No payload signature required. |
Follow the execution Shared process state across worker threads, with explicit exec lifecycle handling. |
Keep the evidence JSONL detections, a live terminal view, and opt-in response after baselining. |
For: security researchers, focused service monitoring, exploit-behavior experiments, and fuzzing triage. Not: a complete EDR, a vulnerability scanner, or proof that a process is uncompromised. Read the threat model before enforcing.
Observe-only by default. Linux x86-64. Controlled deployments should start with a representative benign baseline. Zero false positives and universal exploit coverage are not promised.
Recorded from real local fixture runs; paths abbreviated. PIDs, addresses and counts
vary. Recreate with python3 scripts/record_demo.py. Replayable terminal recording.
The older scan dashboard illustration is illustrative, not a benchmark.
Prerequisites: Linux 5.3+ on x86-64, Rust 1.74+, mounted procfs, and permission to trace your own
child process. WSL2 works; restricted containers may deny ptrace.
git clone https://github.com/grloper/Wraith.git
cd Wraith
cargo build --release --locked
./target/release/wraith doctor
bash demo.shThe demo checks six outcomes, rather than merely printing expected results:
| Local fixture | Required outcome |
|---|---|
benign |
No detections, sensor exit 0 |
benign-threads |
No detections across worker threads, exit 0 |
shellcode-sim |
RWX staging + sensitive syscall from that page, exit 3 |
mt-shellcode-sim |
Same behavior on a worker thread, exit 3 |
shellcode-sim --block |
Syscall returns -ENOSYS; fixture survives, exit 3 |
shellcode-sim --kill |
Traced tree terminated before payload syscall, exit 3 |
These are safe local behavior simulators, not exploitation of a real vulnerability: the payload opens a socket and closes it; it does not connect or exfiltrate data.
On a Debian-family amd64 development machine, build and validate a sensor-only package:
bash scripts/test_deb.sh
sudo apt install ./target/dist/wraith_0.2.0-1_amd64.deb
wraith doctor --jsonBuilding requires dpkg-dev, Python 3 and the Rust toolchain. Installation uses the
ELF dependencies derived from the build system; incompatible older distributions
must rebuild locally. No service, setuid bit, capability grant or security-policy
change is installed. The validation gate inspects/extracts/executes the self-built
package without changing the host package database. This is not official Kali
inclusion or evidence of installation on every Linux distribution.
See Debian package guidance.
# Launch and observe one program and its future children.
./target/release/wraith run -- /usr/bin/your-service --foreground
# Attach to an existing process (ownership / Yama policy applies).
sudo ./target/release/wraith attach 4242
# Observe a selected set, never start with an entire busy host.
sudo ./target/release/wraith scan --match nginx --max-targets 4
# Human-readable output on stderr; append JSONL evidence to a file.
./target/release/wraith run --json events.jsonl -- ./your-target
# Live terminal dashboard.
sudo ./target/release/wraith scan --ui --match nginxSensor exit status: 0 no HIGH/CRITICAL events; 1 HIGH activity; 2 operational or
usage error; 3 CRITICAL activity. This is not the target's exit status. A failed
program can have a clean sensor verdict. --min filters output, not detection or exit status.
| Signal | Default interpretation |
|---|---|
| Sensitive syscall from RWX, executable heap or stack | CRITICAL provenance anomaly |
| Syscall from anonymous RX code, including named anonymous mappings | WARN; legitimate JIT is possible |
mmap / mprotect requests RWX |
HIGH staging request, not proof the kernel accepted it |
| Writable→executable transition | WARN by default (normal W^X JIT behavior); HIGH with explicit no-JIT policy |
| Unregistered stack pointer in heap or a file mapping | HIGH heuristic; successfully observed per-thread signal-stack registrations affect placement only |
| Missing / non-executable origin in a usable map | Coverage uncertainty, not proof of injection |
| Required map refresh fails | Visible coverage gap; no enforcement from stale maps; sensor exit 2 |
| Correlated anomalous sensitive execution and staging | CRITICAL under the current-origin policy; completed staging must cover that address, context expires by syscall budget/time; not proof of data flow |
| Fatal target signal | HIGH crash indicator; crashes are not automatically attacks |
Anonymous RX is not automatically CRITICAL. --jit-critical is an explicit
no-JIT policy: it raises ordinary anonymous-origin calls to HIGH and sensitive ones
to CRITICAL. Use it only when that assumption fits the workload.
--trust-region START-END exempts operator-vouched half-open hexadecimal address
ranges from provenance and fully covered, page-rounded protection requests. Trust is an explicit
blind spot, not automatic JIT attestation. ASLR makes static ranges fragile;
never trust broad ranges merely to make alerts disappear.
--no-stack-pivot disables the custom-stack-sensitive heuristic.
--audit-sensitive adds INFO breadcrumbs for sensitive calls from accepted code.
--correlation-window N sets the evidence budget (default 64 syscall entries,
also expiring after 30 seconds); --max-history N caps retired dashboard rows
(default 128, with live rows preserved). Failed/zero-byte input does not count as
completed input evidence; protection requests may still be reported even if they fail.
See wraith --help and operator guidance.
JSONL events carry schema and sensor versions. A strict, bounded triage helper and investigation workflows validate complete logs without treating a quiet/filtered stream as proof of complete coverage.
# On a CRITICAL event only: skip the current syscall, returning -ENOSYS.
./target/release/wraith run --block -- ./your-target
# On a CRITICAL event only: SIGKILL the traced process tree.
./target/release/wraith run --kill -- ./your-targetEnforcement can break a legitimate application if its memory policy resembles injection. Baseline first, review evidence, then opt in. It is not a substitute for sandboxing.
Linux ptrace syscall stop → registers + cached /proc maps
→ origin / W^X / stack checks
→ per-address-space correlation
→ JSONL + terminal reporter
→ observe (default) / block / kill
- Transport-independent engine:
src/engine.rsowns policy, maps and statistics;src/tracer.rsowns Linux tracing and enforcement. - Sorted interval lookup: provenance uses binary search over parsed mappings.
- Shared process state: worker threads share map and correlation state.
- Small supply chain: no async runtime, TUI framework or serialization dependency.
Every syscall still pays the ptrace stop/resume cost. This is focused monitoring,
not low-overhead whole-host telemetry. Measure your own workload:
python3 scripts/benchmark.py --iterations 20000 --samples 5
python3 scripts/benchmark_workloads.py --helpActual five-pair WSL wall timings (startup/import/shutdown included):
| Owned workload | Baseline median | Traced median | Slowdown |
|---|---|---|---|
| Loopback HTTP, 20 requests | 125.537 ms | 421.720 ms | 3.359× |
| SQLite, 200 transactions | 564.563 ms | 1696.223 ms | 3.004× |
Raw samples and fingerprints and benign runtime controls preserve target and sensor outcomes separately. Python was clean; Java completed with four HIGH RWX-policy findings, not fatal crashes; native Node was unavailable. This is no population false-positive or native-production performance claim. The historical 140.338× syscall-loop result is a different workload, not a before/after speedup comparison.
The script reports raw timings, kernel and median slowdown; it does not manufacture a performance claim. An eBPF backend is not implemented. Observation and synchronous syscall blocking require different kernel mechanisms; see research notes.
bash scripts/verify.sh # formatting, Clippy, strict real-ptrace tests, docs
cargo build --release --locked
./target/release/wraith doctor
bash demo.sh # asserted detection + enforcement demoCI requires actual ptrace execution (WRAITH_REQUIRE_PTRACE=1) rather than silently
accepting skipped integration tests. The verification notes
distinguish measured evidence from untested environments.
Start with CONTRIBUTING.md, the architecture, or small, testable contribution ideas. Report security issues using SECURITY.md. Changes are tracked in CHANGELOG.md.
Want to help? Reproduce a benign false positive, add a regression fixture, or measure an actual service workload. Those contributions matter more than a badge or star count. Launch kit and resume-ready project summary.
MIT · LICENSE