nexec
No-execute bits for LLM tokens. Mark the text that came from outside (tool output, retrieved documents, emails, web pages) and nexec removes its instruction-like component inside the model. The model still reads that text but stops obeying it.
from nexec import NX, untrusted
nx = NX.from_pretrained("meta-llama/Llama-3.2-3B-Instruct") # model + bundled directions
# table: any text from outside (a tool result, a retrieved page) that may carry an injected instruction
nx.generate([{"role": "user", "content": [untrusted(table), "\n\nWhat is the average salary in Marketing?"]}])
Read this first. nexec is a mitigation layer, not a security boundary.
- On BIPIA, 3–8% of injections still get through.
- It acts only on text you mark as untrusted; it does not detect injections.
- It needs open weights in HF Transformers.
Keep sandboxes, tool allowlists and least privilege. See Limitations.
Credit. nexec reimplements NVIDIA's NXDirection (Tamás Vörös, NVIDIA AI Security, Oct 2026, research page). The method is theirs. This repo adds:
- a provenance API;
- a Hugging Face Transformers implementation;
- versioned direction files;
- independent evaluations.
Closest related work:
- CounterSteer (Russinovich, arXiv 2609.36570) subtracts a fixed steering direction from tool-result tokens at prefill.
- Semantic Overlays (Penman, arXiv 2608.23873, code) uses trained adapters to mark spans as non-executable.
All three share the idea of an edit inside the model, gated by provenance.
Install
pip install nexec # or: uv add nexec
pip install "nexec[viz]" # + matplotlib, for PNG heatmaps
Python ≥ 3.10, PyTorch, Transformers 5.x (results measured with 5.20). The bundled models are gated on the Hugging Face Hub: accept the Llama 3.2
or Gemma 2 license there and log in (hf auth login) before the first download. The device is picked
automatically (Apple MPS, CUDA or CPU). All results here were measured on Apple MPS.
To run the demo from a clone (it prints both answers and rewrites the heatmaps):
git clone https://github.com/ThePeeps191/nexec && cd nexec
uv sync && uv run python examples/salary_table.py
Results
All results are on the bundled settings: all layers, α = 1, raw gate for Llama, affine gate for Gemma.
BIPIA. Email, table, abstract and code tasks; 3,300 attacked and 300 clean test samples per condition; Claude Sonnet 5.5 as judge.
| Model | Attack success, undefended → nexec | Random direction, same edit sizes | Clean utility, undefended → nexec |
|---|---|---|---|
| Llama-3.2-3B-Instruct | 22.9% → 8.3% | 22.3% | 55.0% → 55.5% |
| Gemma-2-2B-it | 22.0% → 3.0% | 21.7% | 47.0% → 49.5% |
Clean document QA. 200 SQuAD questions, with the passage marked untrusted.
| Model | Accuracy, undefended → nexec | Paired difference [95% CI] | Identical answers |
|---|---|---|---|
| Llama-3.2-3B-Instruct | 91.0% → 91.0% | 0.0 pt [−1.5, +1.5] | 170/200 |
| Gemma-2-2B-it | 94.0% → 94.5% | +0.5 pt [0.0, +1.5] | 171/200 |
- Attacks that need semantic judging almost vanish: Llama 13.9% → 0.9%, Gemma 15.0% → 0.5%.
- Style-only and code injections partly survive, mostly on Llama. Examples are "use emojis in your answer" and
malicious code pasted into a Stack Overflow answer. Llama's rule-scored attack success only falls 41.6% → 24.7%
(Gemma: 38.1% → 7.6%).
- The direction does recognize these injections; at α = 1 the edit is just too weak for them.
- Stronger settings are opt-in: Llama with the affine gate at α = 4 reaches 0.6%, but costs 5.7 pt of clean utility. Gemma at α = 2 reaches 1.8%.
- The direction is doing the work. A random direction with identical per-token edit sizes does nothing.
- Utility is unchanged within noise on both BIPIA and SQuAD.
- NVIDIA reports lower residuals (0.1% / 0.2%) over 5 tasks, with an unnamed judge. We do not reproduce those.
Reports:
Every generation and verdict is committed under docs/findings/.
Usage
from nexec import NX, untrusted
nx = NX.from_pretrained("google/gemma-2-2b-it")
table = "| Name | Dept | Salary |\n|---|---|---|\n| Bob | Mkt | 82k |\n| Eve | Mkt | 88k |" # untrusted text
out = nx.generate(
[
{"role": "system", "content": "Answer the question using only the table data."},
{"role": "user", "content": [untrusted(table), "\n\nWhat is the average salary in Marketing?"]},
],
max_new_tokens=160,
)
untrusted(...)marks part of a message as data. Messages withrole="tool"are untrusted as a whole.- Pass a list of conversations to batch them.
generatereturns one string per conversation. nx.generate(..., defend=False)runs the same prompt undefended.alpha=andlayers=override the bundled settings.- At a lower level,
nx.encode(...)returnsinput_ids,attention_maskand a token-aligneduntrusted_mask.with nx.protect(mask): model.generate(...)wraps your own generation loop. nexec.vizdraws per-token heatmaps: an interactive HTML layer slider, and PNG with thevizextra.
Bundled directions cover Llama-3.2-3B-Instruct (raw gate) and Gemma-2-2B-it (affine gate).
- They are derived from those models and ship under their terms: the Llama 3.2 Community License ("Built with Llama")
and the Gemma Terms of Use. Copies sit next to each direction file in
nexec/directions/. - For any other Transformers decoder,
NX.from_pretrained(model_id, fit_if_missing=True)fits directions from the packaged corpus and caches them in~/.cache/nexec. That took 21 s for Llama-3.2-3B, model load included, on the M-series Mac used for the results here.scripts/fit_directions.pydoes the same. - Fitted directions are untested on BIPIA for models other than these two.
How it works
- Find the direction, once per model. A small labeled corpus tags tokens as instruction (the user's request)
or content (what the request operates on, even when that content is itself phrased as a command). The direction
is computed per layer:
d = normalize(mean(h | instruction) − mean(h | content)). - Apply it at inference time. For every untrusted token at each layer:
h ← h − α · ReLU(h·d − b) · d.- Tokens that already read as content are left alone.
- Trusted tokens (system prompt, user request, chat template) are never edited.
- Because the edit is in the residual stream, it carries through the KV cache into everything the model generates afterwards.
Limitations
- Mitigation, not a guarantee. On BIPIA, 3–8% of injections still succeed.
- Code-shaped injections and terse style requests are the weakest cases.
- Paraphrased injections are reduced, not eliminated.
- Adaptive attacks have not been evaluated: injections written to read as content, or optimized against the direction itself.
- Keep sandboxes and tool allowlists, and keep secrets out of prompts.
- Needs hidden states. It works with open-weight models in HF Transformers, not with closed APIs. There is no vLLM, llama.cpp or MLX backend yet.
- Provenance is your job. nexec does not detect injections; it only acts on tokens you mark. Tool messages
(
role="tool"/"ipython") are untrusted by default. Any other unknown role must be marked explicitly. - Small models only so far. Evaluated on Llama-3.2-3B-Instruct and Gemma-2-2B-it.
- Utility evidence is task-specific. BIPIA and short extractive QA are flat. On 10 ordinary documents, answers were byte-identical in 9/10 (Llama) and 10/10 (Gemma). Long-form generation and multi-document RAG are untested.
- Special-token text inside untrusted content. A literal string such as
<|eot_id|>inside untrusted content is tokenized as a real control token, exactly asapply_chat_templatedoes. Those tokens are still masked and edited, but strip or escape such strings from external text if your threat model includes forged chat turns. - Not yet supported with
defend=True: beam search andnum_return_sequences > 1.
Development
uv sync # project venv only; nothing is installed globally
uv run pytest # fast tests (tiny random models, CPU); gated-tokenizer tests skip without HF access
uv run pytest -m slow # end-to-end salary demo + heatmap (needs cached weights)
The design spec is in docs/superpowers/specs/. Each step's findings, with raw data, are in docs/findings/.
License
The code is MIT. The bundled direction files are derived from Llama 3.2 and Gemma 2 and ship under those models'
terms (see the NOTICE.md in each directory).
Built with Llama.
Metadata
Release files for nexec 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| nexec-0.1.0.tar.gz | 2.3 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nexec-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.0 MB
Release files / nexec-0.1.0.tar.gz
| Download URL | nexec-0.1.0.tar.gz |
|---|---|
| Size | 2.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8317186ade567024decc93b02a2427ac08028489f895352e1f8ae2beae51bbd6
|
|
BLAKE2b-256 checksum How to use checksums |
c0f5bc20023b1c60a3bda41c25ffe793b12210324b05fd8dc2bbbc73f3f09c16
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / nexec-0.1.0-py3-none-any.whl
| Download URL | nexec-0.1.0-py3-none-any.whl |
|---|---|
| Size | 1.7 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ad1cac0f8b8f936f2c3df6e03bc3bdb46ece3d8898344c836ad75d46dfb1b645
|
|
BLAKE2b-256 checksum How to use checksums |
fee3063e8b20c3882a6eb6a5814f4048b6c457b67c13af9adbddf43ca1eca96c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for nexec 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.