đ Security & sandbox
NanoResearch executes LLM-generated Python code as part of Stage II. Read
this before exposing the API to anything other than 127.0.0.1.
Threat model
We assume the LLM is honest but unreliable:
- â Wonât deliberately exfiltrate data
- â ď¸ Might produce code that opens sockets, writes outside the workspace, spawns runaway loops, or imports forbidden packages
- â ď¸ Might be coerced via prompt injection from retrieved literature abstracts
We do not defend against a malicious LLM with internet egress; that would require a stronger sandbox (Docker / gVisor / firejail) which is on the roadmap.
Current sandbox guarantees
src/nanoresearch/agents/sandbox.py enforces:
| Layer | Mechanism | Effect |
|---|---|---|
| Path traversal | write_files() resolves each target and refuses anything outside the workspace |
LLM cannot drop a file in ~/.ssh |
| Memory | RLIMIT_AS = 2 GB |
OOM-killed after 2 GB |
| CPU time | RLIMIT_CPU and subprocess.run(timeout=âŚ) |
Killed after 240 s by default |
| Network | HTTP_PROXY, HTTPS_PROXY env stripped; NO_PROXY=* |
DNS still resolves; doesnât block raw sockets |
| Filesystem | cwd set to workspace directory |
Relative paths land inside the sandbox |
| Environment | PYTHONSTARTUP, proxies removed; clean child env |
No shell escape via dotfiles |
| Imports | Coding prompt restricts allowed packages | Soft guarantee â debug loop catches violations |
Whatâs NOT blocked
- Raw socket calls (
socket.socket(...).connect(...)) on a network that isnât proxied - Filesystem reads outside the workspace (e.g.,
open('/etc/passwd')) â Stage II output is captured and analysed, but the read itself happens - Forks / subprocesses spawned by the generated code (they inherit the RLIMITs but arenât traced)
Do not run NanoResearch on a machine with secrets that the local user shouldnât see. Run on a personal dev box or inside a VM.
Prompt injection from literature
OpenAlex abstracts are inlined into the Ideation prompt. A maliciously crafted abstract can attempt to redirect the model. We mitigate by:
- Truncating each abstract to ~280 chars in the rendered prompt block
- Always instructing the LLM to âDO NOT copy retrieved content â they are general patternsâ
- Returning only JSON via
response_format={"type":"json_object"}so free-form prose embedded in retrieved text canât escape the schema
If you operate in a high-sensitivity setting, disable literature retrieval or restrict the OpenAlex search to a curated allow-list of paper IDs.
Azure AD auth
- We use
DefaultAzureCredentialâget_bearer_token_providerwith scopehttps://cognitiveservices.azure.com/.default. - Tokens are acquired per-request by the OpenAI SDK; we never log them.
- The runtime needs the Cognitive Services OpenAI User role on the
resource. Anything less will see
401on first call. - No API keys live in
.env, nor inRunManifestevents.
Recommended hardening before exposing
- Put the FastAPI app behind an authenticated reverse proxy (Cloudflare Access, oauth2-proxy, AAD App Proxy).
- Switch the Stage II sandbox to Docker â drop
--network=none,--cap-drop=ALL,--security-opt=no-new-privileges, read-only root filesystem, tmpfs/tmp. - Persist nothing user-supplied beyond
data/users/<id>/andruns/. Both directories are local-only by default. - Rate-limit
POST /api/runsandPOST /api/intent(intent costs LLM tokens; runs cost LLM tokens + CPU).
Reporting a vulnerability
Please email the maintainer listed in pyproject.toml instead of opening
a public issue.