Safety model¶
← Home
nfind runs code written by a language model. That code is never executed directly on
your machine — it runs inside a disposable container locked down on several axes. The
default backend is Docker; macOS users may explicitly opt into Apple Containers with
--sandbox apple. This page explains exactly what each sandbox does and does not
allow.
The threat¶
An LLM-generated filter could, in principle, be wrong, ambiguous, or malicious: it
might try to delete or modify files, exfiltrate data over the network, or consume
unbounded resources. nfind's design assumes the generated code is untrusted and
strictly constrains its blast radius through sandboxing and isolation.
What protects you¶
1. Code generation is separated from execution¶
The prompt is turned into code on the host, where your OpenAI credentials live. Only your natural-language description is sent to the API — never your file list or file contents. The generated code is then shipped into the sandbox to run. The sandbox itself has no credentials. With the default Docker backend it also has no network. With Apple Containers on macOS 15, see the network caveat below.
2. The search tree is mounted read-only¶
The directory you search is bind-mounted into the container with the readonly flag. The
filter can read names, metadata, and contents, but cannot create, modify, or delete
anything in your files.
3. The default Docker container is hardened (providing strong isolation guarantees)¶
This applies to both runtimes: the Python and Node.js base images run under the same restrictions. Each Docker run uses a fresh, throwaway container started with several options that provide strong isolation guarantees when running on Docker:
| Flag | Effect |
|---|---|
--network none |
No network access of any kind. |
--read-only |
The container's own root filesystem is read-only. |
--cap-drop ALL |
All Linux capabilities dropped. |
--security-opt no-new-privileges |
Processes cannot gain new privileges. |
--mount …,readonly |
The search tree is read-only (see above). |
--tmpfs /tmp:…,noexec,nosuid,nodev |
A small scratch space, non-executable. |
--memory, --cpus, --pids-limit, --ulimit nofile |
Bounded memory, CPU, processes, and open files. |
--rm |
The container is removed when it exits. |
The worker also runs as an unprivileged user inside the image. If the filter exceeds
its --timeout, the container is killed.
3b. Apple Containers is an explicit experimental backend¶
--sandbox apple uses Apple's container CLI instead of Docker. nfind uses the same
worker images and keeps the important file-system protections: the search roots are
mounted read-only, the container root filesystem is read-only, capabilities are
dropped, CPU/memory/open-file limits are set, and a tmpfs is provided for scratch
space. On macOS 26+ the backend passes --network none; on older macOS releases it
falls back to --no-dns.
However, this is not security-equivalent to Docker on macOS 15. Apple's official
docs say that on macOS 15 all containers attach to the default vmnet network, the
container network commands are unavailable, and using --network with
container run or container create results in an error. As a result, nfind cannot
pass Docker's --network none on macOS 15. --no-dns only avoids configuring DNS; it
does not prove that raw IP network access is impossible. On macOS 26+ nfind is prepared
to use Apple Containers' --network none support instead. The current Apple CLI also
does not expose Docker-equivalent --pids-limit or
--security-opt no-new-privileges flags. Its --cpus option accepts whole-number CPU
counts only; nfind formats the default 1.0 as 1 and rejects fractional Apple CPU
limits before running the container.
Because of this, Apple Containers is opt-in and prints a warning every time it is used. Use Docker when you need nfind's strongest sandbox. Apple Containers is useful on macOS 15 when you accept this network limitation in exchange for running via Apple's lightweight VM-per-container runtime, and is prepared to use stronger network isolation on macOS 26+.
3c. Podman is an explicit experimental backend¶
--sandbox podman uses the podman CLI. Unlike Apple Containers, Podman is drop-in
compatible with Docker's hardening flags, so nfind builds the same run command it
uses for Docker: --network none, --cap-drop ALL, --security-opt no-new-privileges,
a read-only root filesystem, read-only search-root mounts, pids/memory/CPU/open-file
limits, and a tmpfs. In nfind's code the Docker and Podman run commands are generated by
one shared function, so they cannot drift apart on the security-critical flags.
In particular, network isolation is on par with Docker: --network none gives the
container an empty network namespace with only loopback — no interfaces, no DNS, and no
route out — in both rootless and rootful mode (rootless user-mode networking is bypassed
entirely). This is unlike Apple Containers on macOS 15, which cannot disable networking and
falls back to --no-dns, where raw IP egress may still be possible.
Podman is typically rootless and daemonless, and that changes one thing nfind has to
handle: a rootless container remaps the invoking host user to root inside its user
namespace, so a read-only bind mount of a host-owned directory appears owned by root and
is unreadable by the image's non-root worker — the run would silently find nothing. On
rootless Podman nfind therefore adds a --userns=keep-id mapping onto the worker's own
uid/gid, which makes the mount readable without relaxing any hardening flag. Rootful
Podman needs no remap and is left untouched.
It is still marked experimental: the flag set and this remap have been exercised against a
real rootless Podman machine, but only on limited hosts, and a rootless container's
isolation properties differ from a rootful Docker daemon's — validate it for your threat
model before relying on it. nfind prints a warning each time --sandbox podman is used,
and Docker remains the most thoroughly tested backend.
3d. nerdctl (containerd) is an explicit experimental backend¶
--sandbox nerdctl runs the worker on containerd via the nerdctl CLI (as found under
Lima or Rancher Desktop). nerdctl is Docker-compatible, so nfind builds the same run
command it uses for Docker — generated by the same shared function, including
--network none for full network isolation — so the two cannot drift on the
security-critical flags.
It is exercised end-to-end on Linux CI against a real, rootful containerd — a full image
build plus a hardened container run — in addition to nfind's mocked command tests. It stays
marked experimental for two reasons. First, that validation covers only rootful containerd
on a single CI host. Second, rootless nerdctl (RootlessKit + containerd) remaps the host
user to root inside a user namespace just like rootless Podman, which can leave the
read-only mount unreadable by the non-root worker — but unlike Podman, nerdctl has no
--userns=keep-id mapping to remap it back, so nfind cannot transparently fix rootless
readability here. Prefer rootful containerd (or Docker/Podman). nfind prints a warning each
time --sandbox nerdctl is used.
4. Results can't be forged¶
The host gives the filter a fixed list of paths and verifies that every returned result is one of them. Generated code therefore cannot inject arbitrary paths into the output, and the host maps only known container paths back to host paths.
5. Third-party packages are gated by a whitelist¶
A filter may request PyPI packages (to read MP3 tags, image metadata, and so on).
nfind installs only packages that are approved — a curated built-in list plus
ones you've explicitly approved before, remembered across runs. New packages require
confirmation; --no-deps refuses them entirely. Packages are
installed at image-build time (which needs network). The default Docker container that
runs the filter has no network; the experimental Apple backend provides that guarantee
only on macOS 26+ where --network none is available, not on macOS 15. See
Dependencies & the whitelist.
6. You can review before running¶
For an extra layer of human control, inspect the code before it executes:
--show-codeprints the generated filter.--savewrites it to a self-describing, replayable artifact for review (and later--run). Note that running a saved Python filter directly withuv runexecutes it outside this sandbox — only do so for filters you trust; usenfind --runto replay it sandboxed.--confirm/-ishows it and asks for approval; declining aborts before anything runs.
What this does not protect against¶
- Container-runtime vulnerabilities in Docker, Apple Containers, Podman, or nerdctl. nfind relies on the selected runtime's isolation; keep it updated. For higher assurance, run nfind on a machine where the worst case is acceptable.
- Risk inside an approved package. Approving a package trusts its install-time and import-time behaviour. Only approve packages you recognise; the whitelist limits which packages can enter the image, not what a given package does.
- Apple Containers networking on macOS 15. The Apple backend does not have a
Docker-equivalent
--network nonemode on macOS 15. It warns and uses--no-dns, but raw IP network access may still be possible. - Information disclosure within the mounted tree. The filter can read everything under the path you search. Only point nfind at directories whose contents you're comfortable having an LLM-written script read. Results (paths, and any extra fields) return to your terminal, not to the network.
- Cost or rate limits of the OpenAI API — that's between the host and the API.
Summary¶
The generated code is treated as untrusted. With the default Docker backend, it runs with no network, no write access to your files, dropped privileges, and bounded resources, in a container that is discarded immediately. With Apple Containers on macOS 15, the read-only file protections remain, but no-network isolation is not available; use it only when that tradeoff is acceptable.