Skip to content

Secrets

Rafael Gumieri edited this page May 15, 2026 · 2 revisions

Secrets

Nenya loads secrets from JSON files. Multiple sources are supported with priority ordering.

Secret Paths (Priority Order)

Priority Path Description
1 $CREDENTIALS_DIRECTORY/secrets systemd single file
2 $CREDENTIALS_DIRECTORY/secrets.d/*.json systemd directory
3 $NENYA_SECRETS_DIR/*.json configurable via env var
4 /run/secrets/nenya/*.json K8s/Docker standard path

If multiple JSON files exist in a directory, they are merged alphabetically (last-wins for duplicate keys).

JSON Format

{
  "client_token": "nk-...",
  "provider_keys": {
    "gemini": "AIza...",
    "deepseek": "sk-...",
    "openai": "sk-proj-..."
  },
  "api_keys": {
    "dev-user": {
      "name": "dev-user",
      "token": "nk-...",
      "roles": ["user"],
      "allowed_agents": ["build", "plan"],
      "enabled": true
    }
  }
}
Field Type Required Description
client_token string Yes Bearer token for /v1/* endpoints
provider_keys object No Provider name → API key
api_keys object No Client RBAC keys

Generate Tokens

Client token

openssl rand -hex 32
# Output: abc123def456... (48 hex chars)

Prefix with nk- for convention:

echo "nk-$(openssl rand -hex 32)"

API keys (RBAC)

Nenya supports role-based access control (RBAC) via per-API key configuration. Keys defined in api_keys have roles, agent allowlists, and endpoint allowlists.

Generate a key:

go run ./cmd/nenya keygen --name "dev-user" --roles user,read-only --agents build,plan

Manual configuration:

{
  "api_keys": {
    "dev-user": {
      "name": "dev-user",
      "token": "nk-...",
      "roles": ["user"],
      "allowed_agents": ["build", "plan"],
      "allowed_endpoints": ["GET /v1/models", "POST /v1/chat/completions"],
      "enabled": true
    }
  }
}

Roles:

  • admin — Unrestricted access (bypasses RBAC)
  • user — Access to configured agents, all non-admin endpoints
  • read-only — GET requests only (/v1/models, /healthz, /statsz, /metrics)

Agent Scoping:

  • allowed_agents list restricts which agents the key can access
  • Empty list grants access to all agents (backward compatible)
  • Admin keys bypass agent restrictions

Endpoint Restrictions:

  • allowed_endpoints list allows fine-grained allowlisting (method + path)
  • Overrides default role-based permissions when set
  • Empty list uses role-based default permissions
  • Admin keys bypass endpoint restrictions

systemd

Single file

[Service]
LoadCredential=secrets:/etc/nenya/secrets.json

Directory (merged)

mkdir -p /etc/nenya/secrets.d
cat > /etc/nenya/secrets.d/01-client.json << 'EOF'
{"client_token": "nk-..."}
EOF
cat > /etc/nenya/secrets.d/02-providers.json << 'EOF'
{"provider_keys": {"deepseek": "sk-...", "gemini": "AIza..."}}
EOF

Container / Kubernetes

Docker Compose

services:
  nenya:
    volumes:
      - ./secrets:/run/secrets/nenya:ro
    environment:
      - NENYA_SECRETS_DIR=/run/secrets/nenya
secrets/
├── 01-client.json     → {"client_token": "nk-..."}
├── 02-providers.json  → {"provider_keys": {"deepseek": "sk-..."}}
└── 03-keys.json       → {"api_keys": {...}}

Kubernetes

apiVersion: v1
kind: Secret
metadata:
  name: nenya-secrets
type: Opaque
stringData:
  01-client.json: |
    {"client_token": "nk-..."}
  02-providers.json: |
    {"provider_keys": {"deepseek": "sk-...", "gemini": "AIza..."}}

Mount at /run/secrets/nenya and set NENYA_SECRETS_DIR env var.

Security Notes

  • Never commit secrets to version control
  • Set chmod 600 on secrets files
  • Use systemd credential mechanism for secure in-memory storage
  • Rotate tokens periodically
  • Path traversal (..) is rejected for all secret paths

Secure Memory (mlock)

Nenya uses mlock to keep tokens in RAM, preventing them from swapping to disk. Requires:

[Service]
LimitMEMLOCK=infinity

See Security for implementation details.

Migration from Environment Variables

Environment variables (NENYA_CLIENT_TOKEN, NENYA_PROVIDER_KEY_*) are no longer supported. Migrate to JSON files.

Before (deprecated):

export NENYA_CLIENT_TOKEN="nk-..."
export NENYA_PROVIDER_KEY_DEEPSEEK="sk-..."

After:

// 01-client.json
{"client_token": "nk-..."}

// 02-providers.json
{"provider_keys": {"deepseek": "sk-..."}}

Platform Secret Hardening

Linux (systemd Deployment)

# Restrict secrets directory to root/nenya user
sudo mkdir -p /etc/nenya
sudo chown root:nenya /etc/nenya
sudo chmod 750 /etc/nenya

# Load credentials via systemd (prevents file reads by other processes)
sudo mkdir -p /etc/nenya/secrets.d
sudo chmod 700 /etc/nenya/secrets.d
cat > /etc/nenya/secrets.d/01-client.json << 'EOF'
{"client_token": "nk-..."}
EOF
sudo chmod 600 /etc/nenya/secrets.d/*

The LoadCredential mechanism in systemd passes files via memfd (anonymous memory), avoiding disk reads after boot.

Linux (Standalone Binary)

When running outside systemd, protect the secrets file manually:

mkdir -p creds
chmod 700 creds
cat > creds/secrets.json << 'EOF'
{"client_token": "nk-...", "provider_keys": {"gemini": "..."}}
EOF
chmod 600 creds/secrets.json
CREDENTIALS_DIRECTORY=$(pwd)/creds ./nenya -config config.json

macOS

macOS does not support systemd credentials. Recommended approaches:

Option A: Named file with strict permissions

mkdir -p ~/.config/nenya/secrets.d
chmod 700 ~/.config/nenya/secrets.d
cat > ~/.config/nenya/secrets.d/01-client.json << 'EOF'
{"client_token": "nk-...", "provider_keys": {"gemini": "..."}}
EOF
chmod 600 ~/.config/nenya/secrets.d/*
CREDENTIALS_DIRECTORY=~/.config/nenya/secrets.d ./nenya -config config.json

Option B: macOS Keychain (preferred)

Store the full JSON blob as a generic password in the login keychain:

security add-generic-password -s "nenya-secrets" -a "$USER" \
  -w "$(cat creds/secrets.json)" -U

Retrieve at runtime (note: no helper script yet — use a wrapper):

#!/usr/bin/env bash
# wrapper.sh — retrieve secret from Keychain, then launch nenya
export CREDENTIALS_DIRECTORY=$(mktemp -d)
security find-generic-password -s "nenya-secrets" -a "$USER" -w \
  > "$CREDENTIALS_DIRECTORY/secrets"
chmod 600 "$CREDENTIALS_DIRECTORY/secrets"
exec ./nenya -config config.json

Keychain storage encrypts the secret at rest with the user's login password and prevents accidental exposure via file reads or version control.

See Also

Getting Started

Core Concepts

Reference

Operations

  • Demo — Test all pipeline tiers
  • Troubleshooting — Common issues and solutions
  • FAQ — Frequently asked questions
  • Security — Security policy and vulnerability reporting

Project

Clone this wiki locally