OS Trading Engine
Security
Security Overview

Security Overview

Nexgent is designed with security as a core principle. As a self-hosted trading engine handling real funds, security is critical at every layer.

⚠️

This documentation covers the security architecture. For environment variable configuration, see Configuration > Environment Variables.

Security Principles

PrincipleImplementation
Non-custodialPrivate keys never leave your infrastructure
Defense in depthMultiple security layers (auth, validation)
Least privilegeScoped API keys, role-based access
Secure defaultsSensible defaults, explicit opt-in for risky features

Authentication

Nexgent supports two authentication methods:

JWT Authentication (Dashboard)

For user sessions via the dashboard:

User → Login → Backend validates credentials

              Returns access token (15m) + refresh token (24h)

              Frontend stores tokens

              Requests include: Authorization: Bearer <access_token>

Features:

  • Short-lived access tokens (15 minutes)
  • Refresh token rotation (single-use)
  • Token blacklisting on logout
  • "Remember me" for extended sessions (30 days)

API Key Authentication (Integrations)

For programmatic access:

Client → Request with X-API-Key: nex_xxxxx

              Backend validates key hash

              Checks scopes for endpoint

              Processes request

Features:

  • SHA-256 hashed storage (raw key shown once)
  • Granular scoped permissions
  • Revocable at any time

Authorization

Resource Ownership

All resources are scoped to the authenticated user:

// Every query includes user ID filter
const agents = await prisma.agent.findMany({
  where: { userId: req.user.id },
});

Users can only access:

  • Their own agents
  • Their own wallets
  • Their own transactions
  • Their own API keys

API Key Scopes

ScopePermissions
full_accessFull API access (grants all permissions)
signalsRead & write trading signals
agentsRead agent data & configuration
positionsRead open positions
balancesRead agent balances
transactionsRead transaction history
historyRead historical swaps

Use Restricted mode in the UI to select specific scopes, or Full Access for complete API access.


Password Security

Requirements

  • Minimum 8 characters
  • Maximum 128 characters
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one number
  • At least one special character

Hashing

  • Algorithm: bcrypt
  • Salt rounds: 12 (configurable)
  • Timing-safe comparison: Prevents timing attacks
// Password hashing
const hash = await bcrypt.hash(password, 12);
 
// Verification (constant-time)
const valid = await bcrypt.compare(password, hash);

Token Security

Access Tokens

  • Lifetime: 15 minutes
  • Storage: Client memory (not localStorage)
  • Blacklisting: Immediate invalidation on logout

Refresh Tokens

  • Lifetime: 24 hours (30 days with "remember me")
  • Rotation: Single-use (consumed on refresh)
  • Storage: Redis with TTL
// Token rotation prevents replay attacks
async consumeRefreshToken(jti: string): Promise<string | null> {
  // Atomic get-and-delete
  const luaScript = `
    local value = redis.call("GET", KEYS[1])
    if value then
      redis.call("DEL", KEYS[1])
      return value
    end
    return nil
  `;
  return await client.eval(luaScript, 1, key);
}

Secret Management

Required Secrets

SecretPurposeMin Length
JWT_SECRETSign JWT tokens32 chars
NEXTAUTH_SECRETEncrypt NextAuth sessions32 chars
WALLET_NSolana private keysN/A

Generation

# Generate NEXTAUTH_SECRET (frontend)
pnpm generate-secret
 
# Generate JWT_SECRET (backend)
pnpm generate-secret:backend

Storage

  1. Never commit secrets to version control
  2. Use .env files (gitignored)
  3. Use secrets manager in production (Railway, AWS Secrets, etc.)

Input Validation

All inputs are validated using Zod schemas:

// Shared validation schema
const CreateAgentSchema = z.object({
  name: z.string().min(1).max(100),
  walletAddress: z.string().min(32).max(44),
  tradingMode: z.enum(['live', 'simulation']),
});
 
// Validation in handler
const parsed = CreateAgentSchema.safeParse(req.body);
if (!parsed.success) {
  return res.status(400).json({ error: parsed.error });
}

Security Checklist

Before Deployment

  • Generate unique JWT_SECRET (not example value)
  • Generate unique NEXTAUTH_SECRET
  • Configure CORS_ORIGIN for production domain
  • Set NODE_ENV=production
  • Use HTTPS (TLS termination at load balancer)
  • Configure firewall rules
  • Review wallet private key handling

Ongoing

  • Review access logs
  • Rotate secrets periodically
  • Keep dependencies updated
  • Monitor for security advisories

Security Pages