# Egress modes — what each one does

> ⚠️ **Experimental.** The egress boundary is new and ships as
> defense-in-depth, not an absolute guarantee. It blocks the known data-egress
> paths under the scope you pick, but this is an early version — don't rely on
> it as your *only* protection for highly sensitive data yet. See the threat
> model and limitations linked just below.

LDR's **egress scope** (Settings → *Egress Scope*, or the *Privacy & Egress*
panel on the research form) controls **where your research traffic is allowed
to go** — which search engines run, whether your LLM/embeddings may be cloud
services, and which URLs may be fetched. It's the single switch for *"how much
of this run is allowed to leave my machine?"*

Pick a mode below. The default is **Adaptive**, which just follows your primary
search engine, so most people never need to think about it.

> This page explains the modes for everyday use. For the threat model,
> guarantees, and limitations, see
> [`SECURITY.md`](../SECURITY.md#egress-policy-module) and the technical
> [egress package README](../src/local_deep_research/security/egress/README.md).

## At a glance

| Mode | Search engines | LLM / embeddings | Best for |
|---|---|---|---|
| **Adaptive** *(default)* | follows your primary engine | forced local **only** when the run is private | "just do the sensible thing" |
| **Both** | any classified engine (public + local) | your configured providers (cloud allowed) | mixing public web + your collections |
| **Public only** | public web/academic engines only | your configured providers | public research; your local collections aren't touched |
| **Private only** | local engines only (collections, local SearXNG/Ollama) | **forced local** — cloud blocked | sensitive work that must stay on the machine |
| **Strict** | **only** your one primary engine | your configured providers | a single, exact source with zero expansion |

---

## <a id="adaptive"></a>🔀 Adaptive *(default)*

**Adaptive follows your primary search engine** and resolves to a concrete
mode for each run:

- Primary is a **public** engine (e.g. SearXNG pointed at a public instance,
  arXiv, PubMed) → behaves like **Public only**.
- Primary is a **private** source (a local collection, your library) →
  behaves like **Private only** (and therefore forces local LLM + embeddings).

Why it's the default: you choose a search engine anyway, and the privacy
posture "just matches" it. If you make a **private collection** your primary,
the whole run automatically stays local — nothing leaves the box.

> **Note:** to *mix* a private collection with public engines, or to use a
> cloud LLM on a private collection, switch to **Both** (or mark the collection
> public — see below). Adaptive deliberately narrows to match the primary.

## <a id="both"></a>🌐 Both

Any **classified** engine may run — public web/academic engines **and** your
local collections/library. Your configured LLM and embeddings are used as-is
(cloud allowed unless you tick the *Require local* toggles). This is the
pre-policy behaviour: maximum reach, minimum restriction.

## <a id="public_only"></a>☁️ Public only

Only **public** web/academic engines run; your **local collections are
excluded**. URL fetches are allowed to public hosts and blocked for private
ones. Inference is whatever you configured (cloud allowed). Use it when you
want public research and don't want your private documents queried at all.

## <a id="private_only"></a>🔒 Private only

The privacy mode. **Only local engines** run (collections, library, a local
SearXNG/Ollama). Crucially, it **forces local LLM and embeddings** — cloud
providers (OpenAI, Anthropic, Google, OpenRouter, …) are blocked, so your
query and your retrieved documents never reach a cloud model. Public URL
fetches are blocked, and a process-wide socket guard blocks stray outbound
connections. **Nothing leaves the machine.**

> If you have no local LLM configured, a Private-only run will refuse rather
> than silently fall back to the cloud — that's intentional (fail-closed).

## <a id="strict"></a>🎯 Strict

The tightest mode: **only your single primary engine** runs — no expansion to
any other engine at all. At the URL
layer it behaves like Private-only (private hosts allowed, public blocked), but
it does **not** force local inference — set the *Require local* toggles if you
also want local LLM/embeddings.

---

## Per-collection public/private

Each RAG collection has a **public/private** flag (default **private**):

- A **private** collection is excluded under *Public only* / *Adaptive-public*,
  and when used it forces local inference — its chunks never reach a cloud
  model.
- Mark a collection **public** (the *Public collection* checkbox when creating
  it) only if its contents are non-sensitive and you're happy to process them
  with cloud inference / use them under public scope.

## The two local-inference toggles

Independent of the scope, you can force local inference any time:

- **Require local LLM endpoint** — refuse cloud LLM providers / non-local URLs.
- **Require local embeddings** — refuse cloud embedders, and refuse a
  HuggingFace download for an uncached local model.

Both are **implied automatically** under *Private only* (and Adaptive-private),
which is why those toggles auto-check and lock when you select Private only.

---

## Per-research overrides

The three primary controls — **Egress Scope**, **Require local LLM endpoint**,
and **Require local embeddings** — also appear on the research-form page as
per-run dropdown / checkbox overrides. Values set there apply **only to that
research run** and do **not** persist to the settings database, so you can do a
one-off private run without changing your defaults.

## Audit log

Changes to any `policy.*` key, `llm.require_local_endpoint`,
`llm.allowed_local_hostnames`, or `embeddings.require_local` emit a
`policy_audit=True` log line so administrators can trace configuration changes.
Those audit lines are deliberately filtered out of the WebSocket progress
stream (they never reach browser subscribers).

---

### See also

- [Configuration reference](CONFIGURATION.md#settings-list) — the exact setting
  keys (`policy.egress_scope`, `llm.require_local_endpoint`,
  `embeddings.require_local`, `llm.allowed_local_hostnames`) and their
  auto-generated `LDR_*` environment variables.
- [`SECURITY.md`](../SECURITY.md#egress-policy-module) — threat model,
  guarantees, and caveats (including what this does **not** defend against).
