JD.AI is designed with a security-first approach. All credentials are encrypted at rest, sessions are stored locally, and external connections require explicit opt-in. This guide covers every security surface of a production JD.AI deployment.
JD.AI stores API keys and secrets in an encrypted credential store at ~/.jdai/credentials/. The encryption method is platform-specific:
| Platform | Encryption | Details |
|---|---|---|
| Windows | DPAPI | Per-user encryption tied to the Windows account |
| Linux | AES-256-GCM | Random local keyring with key IDs and rotation support |
| macOS | AES-256-GCM | Random local keyring with key IDs and rotation support |
Credentials are never stored in plain text. The /provider add wizard automatically encrypts keys before writing them to the store.
JD.AI supports optional HashiCorp Vault-backed credential storage:
export JDAI_VAULT_ADDR="https://vault.example.com"
export JDAI_VAULT_TOKEN="s.xxxxx"
export JDAI_VAULT_MOUNT="secret" # optional, default: secret
export JDAI_VAULT_PREFIX="jdai/credentials" # optionalWhen Vault is configured, reads/writes are performed against Vault first with encrypted file-store fallback for availability.
For Kubernetes and containerized deployments, mounted secret files can also be used as a read-only fallback:
export JDAI_CREDENTIALS_MOUNT_PATH="/var/run/secrets/jdai"Credential access operations (get/set/remove/list/rotate) are recorded in:
~/.jdai/credentials/access.audit.log
Entries contain operation type, key hash (not plaintext key), backend (vault/local/mounted), success flag, and timestamp.
When JD.AI needs an API key, it resolves through this priority chain:
- CLI flags —
--api-keypassed at launch (not persisted) - Environment variables — e.g.
OPENAI_API_KEY,ANTHROPIC_API_KEY - Encrypted credential store —
~/.jdai/credentials/ - OAuth session — for Claude Code, GitHub Copilot, Codex (automatic token exchange)
Use the interactive wizard to securely store provider credentials:
# Interactive — prompts for key with masked input
jdai /provider add openai
# The wizard validates the key before storing
# Keys are encrypted immediately and never echoed# Remove stored credentials for a provider
jdai /provider remove openai
# Verify removal
jdai /provider list- API keys are encrypted at rest using platform-native encryption (DPAPI or AES-256-GCM)
- Keys are decrypted in memory only when needed for API calls
- The credential store files have restrictive file permissions (
600on Linux/macOS)
To rotate an API key:
# Remove the old key
jdai /provider remove openai
# Add the new key
jdai /provider add openaiOn Linux/macOS, the credential store also supports local encryption-key rotation with backward-compatible decryption of previously encrypted entries.
- API keys are never written to log files
- Provider detection logs show authentication method but not credentials
- Health check responses include provider status but not key material
- The
/doctorcommand redacts all sensitive values
- All sessions are stored in a local SQLite database at
~/.jdai/sessions.db - No session data is sent to any cloud service or telemetry endpoint
- Session data includes conversation history, token counts, and tool invocations
# Export a session to a JSON file (local only)
jdai /export my-session
# Exports go to ~/.jdai/exports/ by default- Session exports are written to the local filesystem only
- No automatic cloud sync or backup to external services
- Export files contain full conversation history — handle with care
- The SQLite database is stored in the user's home directory with standard file permissions
- No encryption-at-rest for the session database (rely on OS-level disk encryption for sensitive environments)
- Use full-disk encryption (BitLocker, LUKS) for additional protection
JD.AI's Model Context Protocol (MCP) server connections are local-only by default:
- MCP servers run on
localhostand are not exposed to the network - No remote MCP connections are established without explicit configuration
- Tool invocations through MCP are subject to the same permission checks as built-in tools
To connect to a remote MCP server, explicit configuration is required:
{
"MCP": {
"Servers": [
{
"Name": "remote-tools",
"Endpoint": "https://mcp.example.com",
"Transport": "sse",
"ApiKey": "stored-in-credential-store"
}
]
}
}Remote MCP connections should use TLS and authenticate with API keys stored in the encrypted credential store.
- GGUF model files are loaded only from user-specified paths (
~/.jdai/models/by default) - The model directory is configurable via
JDAI_MODELS_DIRenvironment variable - No models are automatically downloaded without explicit user action (
/local download)
- Model downloads go through the
/local downloadcommand which requires user confirmation - HuggingFace downloads use the authenticated API when
HF_TOKENis set - Downloaded files are stored in the configured models directory only
Enable API key authentication for the gateway REST API:
{
"Gateway": {
"Auth": {
"Enabled": true,
"ApiKeys": [
{ "Key": "admin-key-here", "Name": "Admin", "Role": "Admin" },
{ "Key": "operator-key-here", "Name": "Operator", "Role": "Operator" },
{ "Key": "readonly-key-here", "Name": "ReadOnly", "Role": "User" }
]
}
}
}Pass the API key via the X-API-Key header:
curl -H "X-API-Key: admin-key-here" http://localhost:18789/api/agentsFor SignalR WebSocket connections, use the query parameter:
wss://localhost:18789/hubs/agent?api_key=admin-key-here
When enabled, the gateway applies a sliding-window rate limiter:
{
"Gateway": {
"RateLimit": {
"Enabled": true,
"MaxRequestsPerMinute": 60
}
}
}Requests exceeding the limit receive a 429 Too Many Requests response. Rate limiting is keyed on authenticated identity or client IP.
By default, the gateway allows all origins for local development. Restrict CORS in production:
{
"Gateway": {
"Cors": {
"AllowedOrigins": ["https://dashboard.example.com"],
"AllowedMethods": ["GET", "POST", "DELETE"],
"AllowCredentials": true
}
}
}JD.AI Gateway runs on HTTP by default. Use a reverse proxy (Nginx, Caddy, Traefik) for TLS termination in production. See Deployment for reverse proxy configuration examples.
When behind a reverse proxy, configure trusted headers:
{
"Gateway": {
"Server": {
"TrustedProxies": ["10.0.0.0/8", "172.16.0.0/12"],
"ForwardedHeaders": true
}
}
}The gateway respects X-Forwarded-For, X-Forwarded-Proto, and X-Real-IP headers from trusted proxies.
For single-machine deployments, bind the gateway to localhost only:
{
"Gateway": {
"Server": {
"Host": "localhost",
"Port": 18789
}
}
}JD.AI has two separate audit mechanisms that serve different purposes.
Traditional application logs written to standard logging sinks (stdout, file, Seq, etc.). Configured via the Logging section of appsettings.json.
These logs include:
- Provider authentication events (success/failure, no credentials logged)
- Agent spawn and stop events
- Channel connect/disconnect events
- API authentication failures
- Rate limit violations
- Configuration changes via the dashboard
Configure structured logging for production:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"JD.AI.Security": "Information",
"JD.AI.Gateway.Auth": "Warning"
}
}
}Log retention is managed by your log aggregation system (Seq, ELK, CloudWatch, etc.).
Structured events stored in a queryable in-memory circular buffer. Unlike operational logs, audit events are structured, filterable, and accessible through the CLI and REST API in real time.
Buffer characteristics:
- Capacity: approximately 10,000 events
- Overflow: oldest events are evicted when the buffer is full
- Persistence: in-memory only — events are cleared on application restart
Event types recorded:
- Every tool invocation (with outcome:
ok,denied, oruser_denied) - Session create and close events
- Policy denial events
Access methods:
/auditcommand — view recent events interactively with severity filtering (see Commands Reference)GET /api/audit/events— query events via REST API with full filter and pagination supportGET /api/audit/stats— summary counts grouped by severity and action
Example — query warning-level events via REST:
curl http://localhost:18789/api/audit/events?severity=warning&limit=25Example — view recent events in the CLI:
/audit --severity warning
For long-term retention and external sink configuration (file, Elasticsearch, webhook), see Audit Logging.
For the full REST API reference for audit endpoints, see the Gateway API Reference.
- Audit events in the in-memory buffer are cleared on application restart
- Operational log retention is managed by your log aggregation system
- Session data is retained in SQLite indefinitely until manually deleted
- To persist audit events across restarts, configure an external audit sink
Use this checklist for production deployments:
- Enable gateway API key authentication (
Auth.Enabled: true) - Use strong, unique API keys for each role
- Place the gateway behind a TLS-terminating reverse proxy
- Restrict CORS to known dashboard origins
- Bind to
localhostif not serving external clients - Enable full-disk encryption on the host
- Configure rate limiting to prevent abuse
- Route logs to a centralized logging system
- Review provider credentials periodically and rotate as needed
- Restrict MCP servers to local-only unless explicitly required
- Configure an external audit sink (file, Elasticsearch, or webhook) for long-term audit event retention
- Monitor
/api/audit/events?severity=warningperiodically for policy denial activity
- Deployment — reverse proxy and TLS configuration
- Governance — policy enforcement and usage limits
- Gateway Administration — operational management
- Providers — credential resolution and provider setup
- Audit Logging — external sink configuration and event schema
- Gateway API Reference — REST audit endpoints
