Study material
concept 6 · interception and containment

Pretool, defender and daemon: interception, decision and containment

Three pieces that call each other and form the flow: the pretool intercepts the action before it happens, nemesis-defender is the brain that scans and issues the verdict, and the daemon is the background process that watches the filesystem and moves confirmed malware to quarantine.

In 30 seconds

The pretool is a synchronous, fail-closed hook: the IDE calls it before each agent action; it translates the request and hands the content to the engine. nemesis-defender is that engine: the pure function compute_severity decides Clean / Suspicious / Malicious. The daemon is a background process, detached from the shell, watching the filesystem; on confirming hostility it moves the file to quarantine (no deletion) and holds the session for human review.

The pretool (short-lived hook) and the daemon (disk-watching service) share compute_severity; the pretool blocks the write with exit 2, the daemon quarantines malicious files on disk, and the human decides.
Pretool (before, hook) and daemon (after, service) use the same engine; blocking is reversible, which is why the daemon moves instead of deleting.

From scratch: what a daemon is

A daemon is a process that runs continuously in the background, no terminal, no direct interaction, usually started once and alive for as long as the service exists. It is detached from the PID of the shell that launched it: close the terminal and it keeps running. The opposite of a hook, which is born, decides and dies on every invocation. The pretool is a hook (short-lived, write-time, synchronous); the defender-daemon is a service (long-lived, watching file events). That difference in nature is what lets the two cover different windows of time.

Why it matters

The pretool catches the action before the byte touches the disk, the right time to stop a bad write, because blocking is reversible. But what if a hostile file reaches the disk through a path the hook never saw? That is where the daemon comes in: it watches the filesystem and acts afterwards, on the materialized file. Since acting afterwards could mean destroying, the daemon never deletes: it moves to quarantine and calls the human. Intercepting and containing are two beats of the same problem.

Quarantine lifecycle: hostility confirmed; the file is moved to .nemesis/quarantine with a meta.json reason; the session is blocked with exit 2; the human reviews and chooses restore or purge.
The quarantine lifecycle: move, record the reason, hold the session and hand the decision back to the human: restore or purge.

How it correlates

They invoke each other in a chain. The pretool, when it runs, ensures the daemon is up (fire-and-forget) and, on Linux, also ensures the eBPF daemon of group 2. Pretool and daemon share the same scan_content engine (same rule source, same verdict for identical bytes, see the overview). And the severity decision here is the same one group 9 deepens with corroboration and quarantine.

How Nemesis applies it

Interception starts by translating the IDE tool name into a Nemesis intent, which is what normalizes Devin, Claude, Cursor, Codex and others into a single vocabulary:

📄 .nemesis/hooks/nemesis-pretool-check-unix.rs:312-347 · tradução de ferramenta em intenção

let agent_action = match tool_name {
    "Edit" | "Write" | "MultiEdit" => "pre_write_code",
    "Bash" => "pre_run_command",
    "Read" | "Grep" => "pre_read_code",
    // Claude / Codex / Cursor / Antigravity ...
    s if s.starts_with("mcp__") => "pre_mcp_tool_use",
    _ => "pre_write_code",
};

The brain is the pure severity function: probabilistic instruction does not contain; deterministic enforcement does. One confirmatory signal is enough for Malicious; otherwise, the count is of distinct corroborating signal types:

📄 .nemesis/nemesis-defender/src/lib.rs:413-437 · compute_severity (função pura)

fn compute_severity(violations: &[DefenderViolation]) -> Severity {
    if violations.iter().any(|v| CONFIRMATORY_VISITORS.contains(&v.visitor.as_str())) {
        return Severity::Malicious;
    }
    // conta TIPOS distintos de visitor corroborante
    match distinct.len() {
        0 => Severity::Clean,
        1 => Severity::Suspicious,   // heurístico isolado, loga e mantém
        _ => Severity::Malicious,    // 2+ métodos independentes concordam
    }
}

The pretool starts the eBPF daemon fire-and-forget when the kernel supports BPF-LSM, without waiting:

📄 .nemesis/hooks/nemesis-pretool-check-unix.rs:1107-1141 · backstop eBPF (Linux)

let lsm_ok = std::fs::read_to_string("/sys/kernel/security/lsm")
    .map(|s| s.contains("bpf")).unwrap_or(false);
if lsm_ok { /* spawn nemesis-ebpf-daemon --ensure-daemon (não espera) */ }

And containment is movement, not destruction: the daemon moves the confirmed file to a quarantine folder with metadata, and holds the session while anything is pending:

📄 .nemesis/nemesis-defender/src/quarantine.rs:1-11 · mover para quarentena, não deletar

//! em vez de DELETAR arquivos maliciosos, o nemesis-defender os MOVE para
//! .nemesis/quarantine/ e os retém para revisão humana.
//!   - o arquivo original (preservado, inerte) + meta.json (por quê)
//! enquanto houver itens pendentes, o pretool bloqueia a sessão (exit 2).

Decisions and limits

The daemon is security-only: quality rules never reach it. Quality is solved by blocking the write, which is reversible; deleting a file is irreversible, and the daemon does not do that over style rules. The only accepted divergence between pretool and daemon is that quality layer, exclusive to the pretool at write-time.

The daemon starts fire-and-forget so the IDE never stalls, but during install that is forbidden on purpose: a freshly started daemon would quarantine the installer itself. The install does only the essentials, and validation (doctor + pentest) is a manual later step, documented in info-install.txt. Finally, compute_severity counts distinct types of signal, not hits: several matches of the same cause in one file do not wrongly escalate to Malicious.

Self-check

Why does the pretool block writes while the daemon moves to quarantine?

Because the pretool acts before the disk (blocking is reversible) and the daemon acts afterwards, on the materialized file (deleting would be irreversible). Moving preserves the evidence and returns the decision to the human.

What makes the decision deterministic rather than probabilistic?

compute_severity is a pure function: same signals, same verdict. There is no model "maybe"; there is a rule. A prompt instruction can be ignored, the function cannot.

Why a daemon and not another hook?

Because the daemon lives in the background, detached from the shell, watching filesystem events over time, a window the short-lived write-time hook does not cover.

Further reading

Citações verificadas contra o repositório. Voltar ao índice.