Inspiration

People keep generative AI out of their work because of leakage: a name, a diagnosis, a database password pasted into a prompt. A small local model avoids the leak and gives up most of the capability.

Masking a prompt and restoring the answer is a solved problem. What nobody was checking is everything else an agent sends out: the documents it re-sends on every planning turn, its tool-call arguments, and its search queries. MosaicLeaks (Gurung et al., 2026) showed that a research agent's search queries alone can reveal what is in a private document.

So Airlock treats every hop that leaves the machine as an egress event. A small NVIDIA model on the device decides what is private. A frontier-class NVIDIA model in the cloud does the reasoning on the sanitized text. A gate in plain code, not a prompt, checks the exact outbound bytes. The cloud can know the problem, not whose problem it is.

What it does

Point any OpenAI-compatible app at http://127.0.0.1:8787/v1. Before a request leaves your machine:

  1. Detect. Regex and entropy detectors mask secrets and identifiers first. NVIDIA Nemotron-3-Nano-4B, running locally through llama.cpp with a JSON-schema-constrained output, proposes semantic spans (names, organizations, health details, quasi-identifiers) on the masked text. Deterministic Korean rules back it up; NVIDIA GLiNER-PII is an optional second proposer.
  2. Vault. Originals become typed placeholders (<PERSON_1>, <ORG_1>, <SECRET_1>). Quasi-identifiers are generalized (Tulsa → a city in Oklahoma, 마흔다섯 → 40대) and combinations are scored like k-anonymity. The mapping never leaves the device.
  3. Gate. The final outbound JSON is walked string by string. If any vault original, declared term, canary or secret pattern is still present, in any normalized form Airlock knows (case, full-width, look-alike letters, separated jamo, spaced digits, Korean numerals, base64, percent-encoding, nested JSON), the request is blocked with HTTP 422. If the local model is down or answers off-schema, the request is also blocked. Airlock fails closed.
  4. Cloud. The exact bytes that passed the gate go to NVIDIA Nemotron 3 Ultra on Nebius Token Factory.
  5. Rehydrate. Placeholders in the answer, including tool-call arguments and streamed deltas, are mapped back on your machine.
  6. Audit. One record per request stores the outbound payloads verbatim and the gate decision.

Agent mode is the new part. A private research agent reads your local documents, plans with Nemotron 3 Ultra, and searches the web with Tavily. Every hop is gated. A search-intent guard rewrites each query on the device with Nemotron-3-Nano-4B into a generic query, gates the exact Tavily payload, and asks a local intent judge whether it still reveals a private situation. One retry, then that search is blocked and the agent continues without it.

The UI shows three panes per request: What you typed, What the cloud saw, Answer (rehydrated). A Review redactions before sending mode lists every proposed redaction with a before/after preview. Protection levels (strict, balanced, minimal) set the privacy/utility trade-off.

How Airlock uses NVIDIA Nemotron and Nebius Token Factory

The small local model does not solve the task. It only decides what is private. The big cloud model does the reasoning.

Role Model Where it runs
Privacy detector, search rewriter, intent judge, result re-ranker NVIDIA Nemotron-3-Nano-4B (nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF, Q4_K_M) On your machine via llama.cpp, thinking off, JSON-schema output
Optional second PII proposer NVIDIA GLiNER-PII On your machine, CPU
Optional second search-intent judge NVIDIA Nemotron 3.5 Content Safety (custom-policy mode) On your machine via llama.cpp
Reasoning over the sanitized request, agent planning with native tool calling NVIDIA Nemotron 3 Ultra (nvidia/Nemotron-3-Ultra-550b-a55b) Nebius Token Factory, POST /v1/chat/completions
Automatic fallback NVIDIA Nemotron 3 Super (nvidia/nemotron-3-super-120b-a12b) Nebius Token Factory, once, on 5xx
Detector in the hosted demo only NVIDIA Nemotron-3-Nano-30B-A3B Nebius Token Factory (the banner says so)
Attacker and judges in the evaluation NVIDIA Nemotron 3 Ultra Nebius Token Factory

Token Factory is on the execution path of every allowed chat request and every agent planning turn. Airlock forwards tools, tool_choice, reasoning_effort and stream unchanged and uses Ultra's native tool calling for the agent. Nemotron 3 Ultra keeps <PERSON_1>-style placeholders intact, including inside tool arguments and in Korean output, which is what makes local rehydration work. Zero Data Retention is recommended on the account; Airlock's guarantee does not depend on it, since whatever the provider keeps is the sanitized payload in the audit log.

Tavily

Tavily is a functional runtime call in two paths. Private search (POST /v1/search): Nano-4B rewrites the query locally, the gate checks the exact Tavily payload, POST https://api.tavily.com/search runs, and results are re-ranked locally against the private context. Agent search-intent guard: every agent query is detected, rewritten, gated, judged for intent, retried once, then blocked if it still reveals the situation.

Measured on 16 fictional scenarios (8 Korean, 8 English) × 2 modes × 3 passes with an attacker that reads only the Tavily queries: private facts recovered from queries alone fell from 5.6% ± 1.2 to 2.1% ± 2.1, and the private situation inferred from queries alone from 10.4% ± 3.6 to 0.0%. Airlock rewrote 89.5% of queries and blocked 9.9% without lowering the blind rubric score (4.38 ± 0.17 unguarded vs 4.58 ± 0.19). Honest framing: Ultra's own queries were already fairly generic, so the guard removed a small leak; it did not close a large one.

How I built it

Python 3.12, FastAPI, httpx, SQLite, llama.cpp llama-server, the OpenAI SDK as the client contract. Apache-2.0; all data is synthetic. Evaluation: 243 cases (122 Korean, 121 English), four baselines on the same harness, an LLM attacker that sees only the outbound payloads, a situation grader, a blind utility judge and a distortion check. Every number is recomputable from the repo.

Challenges I ran into

  • A 4B model is not a gate. In the spike, Nano-4B alone found 95.1% of English privacy spans but 69.8% of Korean semantic spans and 11% of Korean quasi-identifiers. So deterministic detectors sit before it, Korean rules beside it, and a deterministic gate after it.
  • Placeholders confuse the cloud model. Early answers told users their own number "is a placeholder" and base64-encoded <SECRET_1> into a "fixed" config. The fix was a typed placeholder legend built only from token types already in the payload.
  • Budget. A 3-pass rerun stopped when Token Factory returned HTTP 402 with no warning; the harness now aborts on 402. The whole final measurement cost about $5.1.

Accomplishments that I'm proud of

All 243 cases, 3 independent passes, attacker and judges on Nemotron 3 Ultra. Linkable disclosure: the share of the 98 situation-sensitive cases where an attacker reading only the outbound payloads recovers an identity item and infers the private situation.

System Linkable disclosure Identity leak Usefulness 1-5 Utility ratio Distortion Over-redaction Benign masked
raw (pass-through) 84.7% 98.5% 4.52 1.05 7.7% 0.0% 0.0%
regex only 79.6% 92.7% 3.91 0.87 13.9% 0.4% 7.4%
Presidio + ko/en spaCy 53.1% 76.7% 3.01 0.66 33.7% 16.7% 66.7%
NVIDIA GLiNER-PII alone 9.2% 18.9% 2.69 0.59 24.5% 14.1% 59.3%
Airlock, placeholders (default) 7.5% ± 0.6 3.1% ± 0.3 4.15 ± 0.04 0.95 ± 0.01 14.9% ± 3.0 5.3% ± 0.4 0.0%
  • Linkable disclosure falls from 84.7% to 7.5% at a utility ratio of 0.95. Airlock links less than GLiNER-PII alone (7.5% vs 9.2%) while GLiNER-PII masks 59.3% of benign prompts and Airlock masked none.
  • Agent mode (GLiNER-PII ensemble on): linkable disclosure across all hops falls from 81.2% unguarded to 8.3% ± 3.6; private facts recovered from 79.2% ± 1.0 to 20.5% ± 2.6. With the ensemble off the same evaluation measured 17.2% and 31.2% in two runs, so agent mode should run with it on. The situation is still inferred in 85.4% of unguarded and 79.2% of guarded runs: the cloud has to read "a layoff notice" to help. Airlock removes identity, not the topic.
  • Latency: local overhead p50 4.2 s / p95 9.4 s per chat request; 388 s median per guarded agent run (ensemble on) vs 18 s unguarded.

Known limitation. In an earlier run 3 of 48 guarded agent runs ended blocked without an answer, all in English (a public search result contained vaulted health words; one detector repetition loop). After the fix, the full re-measurement had no blocked English run. One Korean run of 48 still ended blocked: the model's own search query repeated a phrase the detector had vaulted. An identity fact still went out in 8 of 48 guarded runs, mostly the deal code name in a document title.

Caveats. Synthetic data; rules were tuned on a 72-case dev split that is part of the 243, so these are not held-out estimates. One attacker, so these are lower bounds: compare systems with each other, not against zero.

What I learned

  • The most stable number was measuring nothing. My first final run reported ± 0.0 on several columns because passes 2 and 3 reused cached detections (detect time fell from 3,412 ms to 462 ms). The harness now checks pass independence.
  • Deterministic beats clever at the boundary. Every guarantee is a string check on the final bytes; every model is a proposer.

What's next

  • Close the 4 persistently linkable quasi-identifier cases, and the remaining Korean agent block.
  • Detector misses inside documents: 20.5% of private facts still reach the cloud through the agent, and document titles are sent unmasked.
  • A generic HTTP tool wired to the egress policy; inbound prompt-injection checks.
  • Keep the hosted demo free and open until judging ends on 2026-12-15.

Built With

  • docker
  • fastapi
  • fly.io
  • gliner-pii
  • httpx
  • llama.cpp
  • nebius-token-factory
  • nemotron-3-nano-4b
  • nemotron-3-super
  • nemotron-3-ultra
  • nemotron-content-safety
  • nvidia-nemotron
  • openai-sdk
  • pydantic
  • pytest
  • python
  • sqlite
  • tavily
  • uv
  • uvicorn
Share this project:

Updates

Submission history