See API Reference for a matrix of endpoints, auth requirements, and streaming behavior. The NodeTool server uses token-based authentication to secure all endpoints when deployed. For environment variable defaults and precedence, see the Configuration Guide.
Quick Start
The running server enforces authentication when it is in Supabase mode
(both SUPABASE_URL and SUPABASE_KEY set). Without those, it runs in
Local mode: loopback connections are trusted and run as user "1", while
non-loopback requests are rejected.
# Start the server (Local mode — loopback trusted)
nodetool serve
# Enable Supabase mode to enforce auth on every request
export SUPABASE_URL=https://your-project.supabase.co
export SUPABASE_KEY=your-service-role-key
nodetool serve
The SERVER_AUTH_TOKEN / deployment.yaml token described below is generated
and consumed by the @nodetool-ai/deploy tooling; see
Authentication Modes for how the server itself selects
its mode.
How It Works
Token Generation Priority
The server looks for a token in this order:
- Environment variable
SERVER_AUTH_TOKEN - Config file
~/.config/nodetool/deployment.yaml - Auto-generate a new token and save it to the config file
Auto-Generation
On first run, if no token is found:
- A cryptographically secure 32-byte token is generated (
crypto.randomBytes) - The token is saved to
~/.config/nodetool/deployment.yaml - File permissions are set to
0600(owner read/write only) - The full token is displayed in the console output
Token Location
Config file path:
- Linux/macOS:
~/.config/nodetool/deployment.yaml - Windows:
%APPDATA%\nodetool\deployment.yaml
File format:
server_auth_token: your-auto-generated-token-here
Using the Token
All API requests (except the public health endpoints /health, /ready, and
/api/health) require authentication when the server is running in Supabase
mode — that is, when both SUPABASE_URL and SUPABASE_KEY are set. When
those are not set, the server runs in Local mode and accepts loopback
connections without a token for convenience (see
Localhost Trust); non-loopback requests
are rejected with 401.
When authentication is enforced, send the Supabase JWT in the header:
# Get token from config file
TOKEN=$(cat ~/.config/nodetool/deployment.yaml | grep server_auth_token | cut -d' ' -f2)
# Use in requests
curl -H "Authorization: Bearer $TOKEN" http://localhost:7777/v1/models
Example Requests
Chat Completion:
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST http://localhost:7777/v1/chat/completions \
-d '{
"model": "llama3.2:latest",
"messages": [{"role": "user", "content": "Hello"}]
}'
List Collections:
curl -H "Authorization: Bearer $TOKEN" \
http://localhost:7777/admin/collections
Upload File:
curl -H "Authorization: Bearer $TOKEN" \
-X PUT http://localhost:7777/admin/storage/assets/image.png \
--data-binary @image.png
Authentication Modes
The running server does not read an AUTH_PROVIDER variable to choose a
strategy. It selects its mode from the presence of Supabase credentials:
- Supabase mode — enabled when both
SUPABASE_URLandSUPABASE_KEYare set. The server validates a Supabase JWT on every non-public request and enforces auth. - Local mode — the default when those are not set. Loopback connections
bypass auth and run as user
"1"(gated byNODETOOL_TRUST_LOCALHOST); non-loopback requests are rejected with401.
AUTH_PROVIDERis only written into deployed-container environments by the@nodetool-ai/deploytooling. The server itself never branches on it.
To enable Supabase mode:
export SUPABASE_URL=https://your-project.supabase.co
export SUPABASE_KEY=your-service-role-key
Behavior:
- Both HTTP and WebSocket endpoints enforce auth in Supabase mode.
- Clients send
Authorization: Bearer <supabase_jwt>. - In Local mode, loopback connections default to user
"1"and no token is required.
Supabase token verification results are cached in-process. The cache defaults to a 60-second TTL and at most 2000 entries; these are constructor options on the provider and are not configurable via environment variables.
WebSockets use the same
Authorizationheader semantics as HTTP when auth is enforced. A?api_key=<token>query parameter is also accepted as a fallback for WebSocket connections.
Architecture
The authentication system is split across two packages:
@nodetool-ai/auth– defines the abstract AuthProvider base class, token extraction from HTTP and WebSocket headers, and theAuthResult/TokenTypetypes.@nodetool-ai/deploy– provides the concrete token management functions:getServerAuthToken(),generateSecureToken(),verifyServerToken(), and config file I/O.
// @nodetool-ai/auth
abstract class AuthProvider {
extractTokenFromHeaders(headers: Record<string, string> | Headers): string | null;
extractTokenFromWs(headers, queryParams?): string | null;
abstract verifyToken(token: string): Promise<AuthResult>;
}
// @nodetool-ai/deploy
function getServerAuthToken(): string; // resolve token (env → config → generate)
function generateSecureToken(): string; // crypto.randomBytes(32).toString("base64url")
function verifyServerToken(auth: string): Promise<"authenticated">; // timing-safe compare
function loadAuthConfig(): Record<string, unknown>;
function saveAuthConfig(config: Record<string, unknown>): void;
Token verification uses crypto.timingSafeEqual to prevent timing attacks.
Note:
@nodetool-ai/authalso ships aStaticTokenProviderthat readsSTATIC_AUTH_TOKEN/STATIC_AUTH_TOKENS. This is a distinct mechanism from the deploy-packageSERVER_AUTH_TOKENflow, and theStaticTokenProvideris not wired into the running websocket server — the server uses only the Supabase / Local selection described in Authentication Modes.
Environment Variable Override
You can override the auto-generated token by setting the environment variable:
# Set your own token
export SERVER_AUTH_TOKEN="my-custom-secure-token"
# Start server (will use this token instead)
nodetool serve
This is useful for:
- Docker deployments with secrets
- CI/CD pipelines
- Multiple server instances with shared token
Docker Deployment
Using Auto-Generated Token
Mount the config directory as a volume:
docker run -v ~/.config/nodetool:/root/.config/nodetool \
-p 7777:7777 \
nodetool-server
The token persists across container restarts.
Using Environment Variable
# Generate token
TOKEN=$(openssl rand -base64 32)
# Run with token
docker run -e SERVER_AUTH_TOKEN="$TOKEN" \
-p 7777:7777 \
nodetool-server
Docker Compose
version: '3.8'
services:
server:
image: nodetool-server
ports:
- "7777:7777"
volumes:
# Mount config to persist token
- ./config:/root/.config/nodetool
# OR use environment variable
environment:
- SERVER_AUTH_TOKEN=${SERVER_AUTH_TOKEN}
Retrieving Your Token
From Console Output
The full token is displayed when the server starts:
======================================================================
AUTHENTICATION
======================================================================
Status: ENABLED (all endpoints require authentication)
Token: abc12345...xyz9
Source: Auto-generated and saved to /home/user/.config/nodetool/deployment.yaml
Authorization: Bearer abc12345-full-token-here-xyz9
======================================================================
From Config File
# Linux/macOS
cat ~/.config/nodetool/deployment.yaml
# Windows PowerShell
Get-Content $env:APPDATA\nodetool\deployment.yaml
Programmatically
import { getServerAuthToken, getTokenSource } from "@nodetool-ai/deploy";
const token = getServerAuthToken();
const source = getTokenSource(); // "environment" | "config" | "generated"
console.log(`Token (${source}): ${token}`);
Security Best Practices
1. Protect the Config File
The config file contains your authentication token. It’s automatically set to 0600 permissions, but ensure it’s not:
- Committed to version control
- Shared publicly
- Accessible by other users
# Verify permissions (should be -rw-------)
ls -la ~/.config/nodetool/deployment.yaml
# Fix if needed
chmod 600 ~/.config/nodetool/deployment.yaml
2. Rotate Tokens Regularly
# Delete the config file
rm ~/.config/nodetool/deployment.yaml
# Restart server (new token will be generated)
nodetool serve
Or set a new environment variable:
export SERVER_AUTH_TOKEN="$(openssl rand -base64 32)"
3. Use Different Tokens per Environment
# Development
export SERVER_AUTH_TOKEN="dev-token"
# Staging
export SERVER_AUTH_TOKEN="staging-token"
# Production
export SERVER_AUTH_TOKEN="$(openssl rand -base64 32)"
4. Use HTTPS in Production
Always deploy with TLS/SSL:
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:7777;
}
}
Public Endpoints
These endpoints do not require authentication:
GET /health- Health checkGET /ready- Readiness checkGET /api/health- API health check
This allows load balancers and monitoring systems to check service status without authentication.
Error Responses
401 Unauthorized
In Supabase mode, requests without a valid token receive a 401 with an
error field:
Missing token:
{
"error": "Unauthorized"
}
Invalid / rejected token:
{
"error": "<reason from the auth provider>"
}
Non-loopback request when running in Local mode:
{
"error": "Remote access requires authentication"
}
Troubleshooting
Token Not Working
# Start server and check token source in output
nodetool serve
# Look for "Source: ..." in the output
# Verify token in config
cat ~/.config/nodetool/deployment.yaml
# Check environment variable
echo $SERVER_AUTH_TOKEN
Can’t Find Config File
# Check default location
ls -la ~/.config/nodetool/
# Create directory if missing
mkdir -p ~/.config/nodetool
# Restart server to generate token
nodetool serve
Token Not Persisting (Docker)
# Mount config directory as volume
docker run -v nodetool-config:/root/.config/nodetool \
-p 7777:7777 \
nodetool-server
# Or use environment variable
docker run -e SERVER_AUTH_TOKEN="your-token" \
-p 7777:7777 \
nodetool-server
API Client Examples
TypeScript / JavaScript
const TOKEN = "your-token-here";
const BASE_URL = "http://localhost:7777";
const headers = {
"Authorization": `Bearer ${TOKEN}`,
"Content-Type": "application/json",
};
// List models
const models = await fetch(`${BASE_URL}/v1/models`, { headers });
console.log(await models.json());
// Chat completion
const chat = await fetch(`${BASE_URL}/v1/chat/completions`, {
method: "POST",
headers,
body: JSON.stringify({
model: "llama3.2:latest",
messages: [{ role: "user", content: "Hello!" }],
}),
});
console.log(await chat.json());
Python
import requests
TOKEN = "your-token-here"
BASE_URL = "http://localhost:7777"
headers = {"Authorization": f"Bearer {TOKEN}"}
# List models
response = requests.get(f"{BASE_URL}/v1/models", headers=headers)
print(response.json())
# Chat completion
response = requests.post(
f"{BASE_URL}/v1/chat/completions",
headers={**headers, "Content-Type": "application/json"},
json={
"model": "llama3.2:latest",
"messages": [{"role": "user", "content": "Hello!"}]
}
)
print(response.json())
Shell Script
#!/bin/bash
TOKEN=$(cat ~/.config/nodetool/deployment.yaml | grep server_auth_token | awk '{print $2}')
BASE_URL="http://localhost:7777"
# List models
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/v1/models"
# Chat completion
curl -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-X POST "$BASE_URL/v1/chat/completions" \
-d '{
"model": "llama3.2:latest",
"messages": [{"role": "user", "content": "Hello!"}]
}'
Advanced Configuration
Custom Config Location
import { loadAuthConfig, saveAuthConfig, generateSecureToken } from "@nodetool-ai/deploy";
// Load current config
const config = loadAuthConfig();
// Generate and save a new token
config["server_auth_token"] = generateSecureToken();
saveAuthConfig(config);
Localhost Trust and Reverse Proxies
By default the server bypasses authentication for connections from loopback
(127.0.0.1/::1) and runs them as user 1. This is convenient for the
desktop app and local development, but dangerous behind a reverse proxy,
container network, or SSH tunnel, where the proxy itself connects from
loopback — a blanket bypass would silently disable auth in exactly the
deployment that needs it.
To make this safe:
- The loopback bypass is gated by
NODETOOL_TRUST_LOCALHOST. It defaults off whenever auth is enforced (Supabase mode) and on otherwise. Set it explicitly (true/false) to override. X-Forwarded-Foris trusted only for proxies listed inNODETOOL_TRUSTED_PROXIES(comma-separated IPs/CIDRs). When unset, the header is ignored and the unspoofable socket peer address is used to identify the client, so a remote client cannot forge a loopback origin.
# Behind an nginx reverse proxy on the same host, enforcing Supabase auth:
export SUPABASE_URL=... SUPABASE_KEY=...
export NODETOOL_TRUSTED_PROXIES=127.0.0.1 # trust the local proxy's XFF
# NODETOOL_TRUST_LOCALHOST stays off — clients must present a valid token.
Local mode in Docker
Docker NATs a published-port connection to the bridge gateway (e.g.
172.17.0.1), so in Local mode the request never arrives from loopback and the
NODETOOL_TRUST_LOCALHOST bypass can’t fire — the UI loads (static assets and
/api/config are public) but every API/WebSocket call returns
401 "Remote access requires authentication".
NODETOOL_TRUST_LOCAL_NETWORKS (comma-separated source CIDRs) restores the
single-local-user model by trusting those sources as user "1" without a token.
It is honored only in Local mode — in Supabase mode it is ignored so every
request must present a valid token.
⚠️ This bypasses authentication
Every source IP in
NODETOOL_TRUST_LOCAL_NETWORKSis trusted as admin user"1"with no password — full access to your workflows, files, stored secrets, and API keys. Anyone who can reach the published port from a listed range gets that access.
172.16.0.0/12— the Docker bridge range. Host and LAN clients reach the app through the port mapping, but nothing off the bridge is trusted. Use this.0.0.0.0/0— trusts every source, the whole internet if the port is reachable. Never use this on a public IP. Only on a network you fully control (private LAN / VPN), ideally with the port firewalled.- Exposing NodeTool to the internet or untrusted users? Do not widen this list — enable Supabase auth so every request needs a real login.
# Docker self-host, single user, no login (safe on a private LAN):
NODETOOL_TRUST_LOCAL_NETWORKS=172.16.0.0/12 # the Docker bridge range
Security Hardening
- Production: enable Supabase mode by setting
SUPABASE_URLandSUPABASE_KEY, terminate TLS in front of all non-public endpoints, and rotate Supabase service-role keys via your secrets manager. - Localhost trust: keep
NODETOOL_TRUST_LOCALHOSToff in any reverse-proxied or containerized deployment, and list only your real proxies inNODETOOL_TRUSTED_PROXIES. - Staging: keep asset buckets private or signed, and run workflows in subprocess or Docker isolation.
- Development: restrict Local mode to isolated machines and avoid storing real secrets in
.env.development.
See Security Hardening for detailed checklists across dev, staging, and production.