Skip to content

Instantly share code, notes, and snippets.

@ChakshuGautam
Last active November 24, 2025 05:08
Show Gist options
  • Select an option

  • Save ChakshuGautam/c5ac752a756d624a2f0e359ec1e1bed7 to your computer and use it in GitHub Desktop.

Select an option

Save ChakshuGautam/c5ac752a756d624a2f0e359ec1e1bed7 to your computer and use it in GitHub Desktop.
LibreChat Security & Privacy Documentation - Complete security assessment, authentication methods, and GDPR compliance

LibreChat Security & Privacy Documentation

Version: v0.8.1-rc1 Repository: github.com/danny-avila/LibreChat Last Updated: 2025-11-24


Table of Contents

  1. Overview
  2. Authentication
  3. Authorization & Access Control
  4. Input Validation & Sanitization
  5. API Security
  6. Data Encryption
  7. Session Management
  8. Rate Limiting & DDoS Protection
  9. Privacy & Data Protection
  10. GDPR Compliance
  11. Security Configuration
  12. Security Assessment

Overview

🏗️ For overall system architecture and component interactions, see: LibreChat Architecture Documentation

LibreChat implements a comprehensive multi-layered security architecture with:

  • 7 authentication methods (Local, JWT, OAuth, LDAP, SAML, OpenID, 2FA)
  • Role-based access control (RBAC) with granular permissions
  • Modern encryption (AES-256-CTR)
  • GDPR-compliant data handling
  • Rate limiting across all sensitive endpoints
  • Input validation with injection prevention

Security Architecture

graph TB
    subgraph "Authentication Layer"
        A1[Local Auth]
        A2[OAuth 2.0]
        A3[LDAP/SAML]
        A4[OpenID Connect]
        A5[2FA/TOTP]
    end

    subgraph "Authorization Layer"
        B1[RBAC System]
        B2[Permission Service]
        B3[ACL Entries]
        B4[Group Management]
    end

    subgraph "Data Protection"
        C1[AES-256-CTR Encryption]
        C2[Password Hashing bcrypt]
        C3[Secure Sessions]
        C4[HttpOnly Cookies]
    end

    subgraph "Input Security"
        D1[Schema Validation]
        D2[MongoDB Sanitize]
        D3[File Upload Validation]
        D4[XSS Prevention]
    end

    subgraph "Rate Limiting"
        E1[Login Limiter]
        E2[Registration Limiter]
        E3[Message Limiter]
        E4[Auto-Ban System]
    end

    A1 & A2 & A3 & A4 --> A5
    A5 --> B1
    B1 --> B2 --> B3
    B2 --> B4
    C1 & C2 & C3 & C4 --> D1
    D1 & D2 & D3 & D4 --> E1
    E1 & E2 & E3 --> E4
Loading

Authentication

1. Local Authentication (Email/Password)

File: api/strategies/localStrategy.js

Password Security

Hashing: api/server/services/AuthService.js#L245-L249

// bcrypt with salt rounds = 10
const salt = bcrypt.genSaltSync(10);
const hashedPassword = bcrypt.hashSync(password, salt);

Validation: api/strategies/localStrategy.js#L21-L45

// Password comparison with timing attack mitigation
if (!user || !bcrypt.compareSync(password, user.password)) {
  return done(null, false, {
    message: 'Email or password is incorrect.'
  });
}

Password Policy: api/strategies/validators.js#L29-L35

password: z
  .string()
  .min(MIN_PASSWORD_LENGTH)  // Default: 8, configurable
  .max(128)
  .refine(val => val.trim() !== '', {
    message: 'Password cannot be whitespace only'
  })

Email Verification

Verification Flow: api/server/services/AuthService.js#L278-L297

  • Cryptographically secure token generation via webcrypto.getRandomValues()
  • Token hashing before storage with SHA-256
  • 15-minute expiration (expiresIn: 900)
  • One-time use enforcement

Configuration:

ALLOW_EMAIL_LOGIN=true              # Enable email/password auth
ALLOW_UNVERIFIED_EMAIL_LOGIN=false  # Require email verification

2. JWT (JSON Web Tokens)

File: api/strategies/jwtStrategy.js

Token Generation

Access Token: packages/api/src/crypto/jwt.ts#L15-L28

// Short-lived token (default: 15 minutes)
const payload = {
  id: userId,
  username,
  provider,
  email
};

const token = jwt.sign(payload, jwtSecret, {
  expiresIn: SESSION_EXPIRY
});

Refresh Token: api/server/services/AuthService.js#L99-L124

// Long-lived token (default: 7 days)
const refreshToken = jwt.sign(
  { id: session._id },
  jwtRefreshSecret,
  { expiresIn: REFRESH_TOKEN_EXPIRY }
);

Token Validation

Strategy: api/strategies/jwtStrategy.js#L8-L33

// Bearer token extraction from Authorization header
passport.use(new JwtStrategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.JWT_SECRET
}, async (payload, done) => {
  // Validate user exists and is active
}));

Configuration:

JWT_SECRET=your-secret-key            # Access token secret
JWT_REFRESH_SECRET=your-refresh-key   # Refresh token secret
SESSION_EXPIRY=900                    # 15 minutes
REFRESH_TOKEN_EXPIRY=604800           # 7 days

3. OAuth 2.0 & Social Logins

Supported Providers:

Security Features:

  • State parameter for CSRF protection
  • Callback URL validation
  • Scope limitation to necessary permissions
  • User profile mapping with validation

Configuration:

ALLOW_SOCIAL_LOGIN=true
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=/oauth/google/callback

4. LDAP Authentication

File: api/strategies/ldapStrategy.js

Security Features

TLS/SSL: ldapStrategy.js#L20-L35

{
  url: process.env.LDAP_URL,
  tlsOptions: {
    rejectUnauthorized: LDAP_TLS_REJECT_UNAUTHORIZED,
    ca: [fs.readFileSync(LDAP_CA_CERT_PATH)]
  },
  starttls: LDAP_STARTTLS === 'true'
}

Features:

  • START TLS support
  • Certificate validation
  • Custom CA certificate support
  • LDAP search filter configuration
  • Attribute mapping (email, username, name, ID)

Configuration:

LDAP_URL=ldap://your-ldap-server:389
LDAP_BIND_DN=cn=admin,dc=example,dc=com
LDAP_BIND_CREDENTIALS=password
LDAP_USER_SEARCH_BASE=ou=users,dc=example,dc=com
LDAP_SEARCH_FILTER=(uid={{username}})
LDAP_STARTTLS=true
LDAP_TLS_REJECT_UNAUTHORIZED=true
LDAP_CA_CERT_PATH=/path/to/ca-cert.pem

5. SAML (Security Assertion Markup Language)

File: api/strategies/samlStrategy.js

Security Features

Certificate Validation: samlStrategy.js#L25-L35

// Supports RFC7468 format or Base64 encoded
const cert = process.env.SAML_CERT;
const formattedCert = cert.includes('BEGIN CERTIFICATE')
  ? cert
  : `-----BEGIN CERTIFICATE-----\n${cert}\n-----END CERTIFICATE-----`;

Signature Validation:

  • Assertion-signed authentication
  • Response-signed authentication
  • Configurable claim extraction

Configuration:

SAML_ENTRY_POINT=https://idp.example.com/sso
SAML_ISSUER=https://your-app.com
SAML_CALLBACK_URL=https://your-app.com/auth/saml/callback
SAML_CERT=MIIDXTCCAkWgAwIBAgIJAKoSdj...

6. OpenID Connect

File: api/strategies/openidStrategy.js

Advanced Security Features

PKCE Support: openidStrategy.js#L45-L50

// Proof Key for Code Exchange (for public clients)
{
  usePKCE: process.env.OPENID_USE_PKCE === 'true',
  state: true,
  nonce: true
}

Clock Tolerance: openidStrategy.js#L42

  • Configurable clock skew tolerance (default: 300 seconds)
  • Prevents timing-based authentication failures

Token Exchange: openidStrategy.js#L85-L120

  • On-behalf-of flow for Microsoft Graph
  • Secure token storage in cookies
  • Access token refresh capability

Required Roles: openidStrategy.js#L65-L75

// Validate user has required role
if (REQUIRED_ROLE && !userRoles.includes(REQUIRED_ROLE)) {
  throw new Error('User does not have required role');
}

Configuration:

OPENID_ISSUER=https://accounts.google.com
OPENID_CLIENT_ID=your-client-id
OPENID_CLIENT_SECRET=your-client-secret
OPENID_CALLBACK_URL=/oauth/openid/callback
OPENID_SCOPE=openid profile email
OPENID_USE_PKCE=false
OPENID_REQUIRED_ROLE=admin

7. Two-Factor Authentication (2FA)

File: api/server/services/twoFactorService.js

TOTP (Time-based One-Time Password)

Secret Generation: twoFactorService.js#L10-L18

// 80 bits of randomness, Base32 encoded
const buffer = new Uint8Array(10);
crypto.getRandomValues(buffer);
const secret = encode(buffer).toString();

Verification: twoFactorService.js#L25-L40

// HMAC-SHA1, 30-second time step, ±1 window
const isValid = authenticator.verify({
  token: userToken,
  secret: totpSecret
});

Specifications:

  • Algorithm: HMAC-SHA1
  • Time step: 30 seconds
  • Code length: 6 digits
  • Window: ±1 step (60-second acceptance)

Backup Codes

Generation: api/server/controllers/TwoFactorController.js#L105-L120

// 10 codes, 8-character hex strings
const backupCodes = Array.from({ length: 10 }, () =>
  Array.from(crypto.getRandomValues(new Uint8Array(4)))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('')
);

// Store SHA-256 hashed
const hashedCodes = backupCodes.map(code =>
  createHash('sha256').update(code).digest('hex')
);

Validation: api/server/controllers/TwoFactorController.js#L185-L205

  • One-time use enforcement
  • Marked as used with timestamp
  • Cannot be reused

2FA Login Flow

Temporary Token: api/server/controllers/auth/LoginController.js#L65-L75

// 5-minute temporary token for 2FA challenge
const tempToken = jwt.sign(
  { userId: user._id, temp: true },
  JWT_SECRET,
  { expiresIn: 300 }
);

Token Upgrade: api/server/controllers/TwoFactorController.js#L225-L245

  • Validate TOTP or backup code
  • Upgrade to full authentication token
  • Create session

Authorization & Access Control

1. Role-Based Access Control (RBAC)

File: packages/api/src/app/permissions.ts

System Roles

enum SystemRoles {
  ADMIN = 'ADMIN',
  USER = 'USER'  // Default
}

First User Admin: api/server/services/AuthService.js#L265-L270

// First registered user automatically becomes ADMIN
const isFirstUser = (await User.countDocuments()) === 0;
const role = isFirstUser ? SystemRoles.ADMIN : SystemRoles.USER;

Admin Middleware

File: api/server/middleware/roles/admin.js

Enforcement: admin.js#L5-L15

function requireAdminRole(req, res, next) {
  if (req.user.role !== SystemRoles.ADMIN) {
    return res.status(403).json({
      message: 'Admin access required'
    });
  }
  next();
}

2. Permission Service

File: api/server/services/PermissionService.js

Permission Types

enum PermissionTypes {
  // Resource types
  AGENTS = 'agents',
  PROMPTS = 'prompts',
  FILES = 'files',
  CONVERSATIONS = 'conversations',

  // Principal types
  USER = 'USER',
  GROUP = 'GROUP',
  ROLE = 'ROLE',
  PUBLIC = 'PUBLIC'
}

Access Roles

enum AccessRoles {
  VIEWER = 'VIEWER',    // Read-only
  EDITOR = 'EDITOR',    // Read-write
  OWNER = 'OWNER'       // Full control
}

Permission Checking

Check Access: PermissionService.js#L45-L85

async checkPermission({
  userId,
  resourceType,
  resourceId,
  requiredRole = 'VIEWER'
}) {
  // Check direct user permission
  // Check group permissions
  // Check role permissions
  // Check public permissions
  return hasPermission;
}

3. Access Control Lists (ACL)

File: api/models/AccessControl.js

ACL Entry Structure

{
  principalType: 'USER|GROUP|ROLE|PUBLIC',
  principalId: ObjectId,
  resourceType: 'agents|prompts|files|conversations',
  resourceId: ObjectId,
  role: 'VIEWER|EDITOR|OWNER',
  permissions: {
    read: Boolean,
    write: Boolean,
    delete: Boolean,
    share: Boolean
  }
}

4. Feature-Level Permissions

File: packages/api/src/app/permissions.ts#L15-L35

Configurable Features:

interface InterfacePermissions {
  prompts: boolean;
  bookmarks: boolean;
  memories: boolean;
  multiConvo: boolean;
  agents: boolean;
  temporaryChat: boolean;
  runCode: boolean;
  webSearch: boolean;
  peoplePicker: boolean;
  marketplace: boolean;
  fileSearch: boolean;
  fileCitations: boolean;
}

Permission Enforcement:

  • Loaded from configuration
  • Checked at API level
  • UI elements conditionally rendered

Input Validation & Sanitization

1. Schema Validation (Zod)

File: api/strategies/validators.js

Registration Validation

Schema: validators.js#L50-L75

const registerSchema = z.object({
  email: z.string().email(),
  password: z.string()
    .min(MIN_PASSWORD_LENGTH)
    .max(128)
    .refine(val => val.trim() !== ''),
  name: z.string().min(3).max(80),
  username: z.string()
    .min(2).max(80)
    .regex(usernamePattern)
    .refine(detectInjection)
    .refine(val => val.trim() !== '')
});

Username Validation

Pattern: validators.js#L12-L25

// Allowed character sets
const allowedCharSets = [
  '\\p{Script=Latin}',      // Latin
  '\\p{Script=Cyrillic}',   // Cyrillic
  '\\p{Script=Devanagari}', // Devanagari
  '\\p{Script=Han}',        // Chinese
  '\\p{Script=Arabic}',     // Arabic
  '\\p{Script=Hiragana}',   // Japanese
  '\\p{Script=Katakana}',   // Japanese
  '\\p{Script=Hangul}'      // Korean
];

const usernamePattern = new RegExp(
  `^[${allowedCharSets.join('')}0-9_.-]+$`,
  'u'
);

Injection Detection

Pattern: validators.js#L30-L45

const injectionPatterns = [
  "'", "--",           // SQL injection
  "$ne", "$gt", "$lt", "$or",  // NoSQL injection
  "{", "}",            // Object injection
  "*", ";",            // Wildcard/command injection
  "<", ">", "/",       // XSS attempts
  "="                  // Assignment attempts
];

function detectInjection(value) {
  return !injectionPatterns.some(pattern =>
    value.includes(pattern)
  );
}

2. MongoDB Sanitization

File: api/server/index.js#L84

app.use(mongoSanitize());

Protection:

  • Removes $ operators from input
  • Removes . from keys
  • Prevents MongoDB operator injection

3. File Upload Security

File: api/server/routes/files/multer.js

Filename Sanitization

Function: multer.js#L15-L25

function sanitizeFilename(filename) {
  return filename
    .replace(/[^a-zA-Z0-9._-]/g, '_')  // Remove special chars
    .replace(/\.{2,}/g, '.')           // Prevent path traversal
    .substring(0, 255);                 // Limit length
}

MIME Type Validation

Configuration: multer.js#L35-L55

const allowedMimeTypes = {
  images: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
  documents: ['application/pdf', 'text/plain', 'application/msword'],
  audio: ['audio/mpeg', 'audio/wav', 'audio/ogg']
};

fileFilter: (req, file, cb) => {
  const endpoint = req.body.endpoint;
  const allowed = allowedMimeTypes[endpoint] || [];

  if (allowed.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('Invalid file type'), false);
  }
}

File Size Limits

Per Endpoint: multer.js#L60-L75

limits: {
  fileSize: endpointConfig.fileSizeLimit || 20 * 1024 * 1024  // 20MB default
}

4. XSS Prevention

Cookie Security

File: api/server/services/AuthService.js#L45-L65

const cookieOptions = {
  httpOnly: true,      // Prevents JavaScript access
  secure: isProduction,  // HTTPS only in production
  sameSite: 'strict',  // CSRF protection
  maxAge: 7 * 24 * 60 * 60 * 1000  // 7 days
};

res.cookie('refreshToken', token, cookieOptions);

Field Exclusion

User Data: api/server/controllers/UserController.js#L25-L35

// Exclude sensitive fields from responses
user.select('-password -totpSecret -backupCodes');

API Security

1. JWT Authentication

Middleware: api/server/middleware/requireJwtAuth.js

Token Verification: requireJwtAuth.js#L10-L30

function requireJwtAuth(req, res, next) {
  passport.authenticate('jwt', { session: false }, (err, user) => {
    if (err || !user) {
      return res.status(401).json({
        message: 'Unauthorized'
      });
    }
    req.user = user;
    next();
  })(req, res, next);
}

Protected Routes:

  • /api/user/* - User profile and settings
  • /api/messages/* - Message operations
  • /api/convos/* - Conversation management
  • /api/agents/* - Agent management
  • /api/files/* - File operations
  • All routes except /api/auth/* and /api/config

2. API Key Management

File: api/server/services/UserService.js

Key Encryption

Storage: UserService.js#L85-L105

const encryptedKey = encrypt(apiKey);
await UserKey.create({
  userId,
  key: encryptedKey,
  expiresAt: expirationDate
});

Retrieval: UserService.js#L120-L135

const userKeys = await UserKey.find({
  userId,
  expiresAt: { $gt: new Date() }
});

return userKeys.map(k => ({
  ...k.toObject(),
  key: decrypt(k.key)
}));

Key Expiration

  • Configurable expiration dates
  • Automatic cleanup of expired keys
  • Per-user key isolation

3. CORS Configuration

File: api/server/index.js#L85

app.use(cors());

Headers:

  • Default CORS policy (no restrictions in development)
  • Production should configure specific origins
  • Credentials support enabled for cookie-based auth

Data Encryption

1. Encryption Implementations

File: packages/api/src/crypto/encryption.ts

v3 Encryption (Recommended)

Algorithm: AES-256-CTR

Implementation: encryption.ts#L85-L115

function encryptV3(text: string): string {
  // Validate 32-byte key (64 hex chars)
  if (CREDS_KEY.length !== 64) {
    throw new Error('CREDS_KEY must be 64 hex chars (32 bytes)');
  }

  // Generate random 16-byte IV
  const iv = webcrypto.getRandomValues(new Uint8Array(16));

  // Create cipher
  const cipher = createCipheriv(
    'aes-256-ctr',
    Buffer.from(CREDS_KEY, 'hex'),
    iv
  );

  // Encrypt
  const encrypted = Buffer.concat([
    cipher.update(text, 'utf8'),
    cipher.final()
  ]);

  // Return format: v3:IV:encrypted_data
  return `v3:${iv.toString('hex')}:${encrypted.toString('hex')}`;
}

Decryption: encryption.ts#L120-L145

function decryptV3(encryptedText: string): string {
  // Parse format: v3:IV:encrypted_data
  const [version, ivHex, encryptedHex] = encryptedText.split(':');

  const decipher = createDecipheriv(
    'aes-256-ctr',
    Buffer.from(CREDS_KEY, 'hex'),
    Buffer.from(ivHex, 'hex')
  );

  const decrypted = Buffer.concat([
    decipher.update(Buffer.from(encryptedHex, 'hex')),
    decipher.final()
  ]);

  return decrypted.toString('utf8');
}

Features:

  • Random IV per encryption
  • 256-bit key strength
  • CTR mode (parallelizable)
  • Version prefix for migration support

v2 Encryption (Legacy)

Algorithm: AES-CBC with random IV

Format: IV:encrypted_data (hex-encoded)

v1 Encryption (Legacy, Deprecated)

Algorithm: AES-CBC with fixed IV Security Risk: Fixed IV allows pattern analysis Recommendation: Migrate to v3

2. Encryption Key Management

Configuration:

CREDS_KEY=64_character_hex_string  # 32 bytes for AES-256
CREDS_IV=32_character_hex_string   # 16 bytes (only for v1/v2)

Key Validation: encryption.ts#L20-L30

if (!CREDS_KEY || !CREDS_IV) {
  throw new Error('CREDS_KEY and CREDS_IV must be set');
}

// v3 requires 32-byte (64 hex char) key
if (useV3 && CREDS_KEY.length !== 64) {
  throw new Error('v3 encryption requires 64-character hex key');
}

3. Encrypted Data

What's Encrypted:

  • API keys (OpenAI, Anthropic, etc.)
  • OAuth tokens
  • Refresh tokens
  • User credentials for external services
  • LDAP/SAML service account credentials

Storage:

  • MongoDB with encrypted fields
  • Decrypted only when needed
  • Never logged or exposed in responses

Session Management

1. Session Creation

File: api/server/services/AuthService.js

Session Model: AuthService.js#L75-L95

const session = await Session.create({
  userId: user._id,
  expiresAt: new Date(Date.now() + SESSION_EXPIRY * 1000)
});

const refreshToken = jwt.sign(
  { id: session._id },
  jwtRefreshSecret,
  { expiresIn: REFRESH_TOKEN_EXPIRY }
);

2. Token Refresh

Flow: AuthService.js#L150-L190

async refreshToken(refreshToken) {
  // Verify refresh token
  const decoded = jwt.verify(refreshToken, jwtRefreshSecret);

  // Validate session exists and not expired
  const session = await Session.findById(decoded.id);
  if (!session || session.expiresAt < new Date()) {
    throw new Error('Session expired');
  }

  // Generate new access token
  const newToken = jwt.sign(
    { id: session.userId },
    JWT_SECRET,
    { expiresIn: SESSION_EXPIRY }
  );

  return newToken;
}

3. Session Cleanup

Logout: AuthService.js#L195-L215

async logoutUser(sessionId) {
  // Delete session from database
  await Session.deleteOne({ _id: sessionId });

  // Clear cookies
  res.clearCookie('refreshToken');
  res.clearCookie('token_provider');
  res.clearCookie('openid_access_token');
  res.clearCookie('openid_user_id');
}

Automatic Cleanup:

  • MongoDB TTL index on expiresAt field
  • Expired sessions automatically deleted
  • Orphaned sessions cleaned on user deletion

Rate Limiting & DDoS Protection

1. Login Rate Limiting

File: api/server/middleware/limiters/loginLimiter.js

Configuration: loginLimiter.js#L8-L25

const loginLimiter = rateLimit({
  windowMs: LOGIN_WINDOW * 60 * 1000,  // Default: 5 minutes
  max: LOGIN_MAX,                       // Default: 7 attempts
  message: 'Too many login attempts',
  standardHeaders: true,
  legacyHeaders: false,
  handler: async (req, res) => {
    await logViolation(req, {
      user_id: req.body.email,
      type: ViolationTypes.LOGINS,
      score: LOGIN_VIOLATION_SCORE
    });
    res.status(429).json({ message: 'Too many login attempts' });
  }
});

Environment Variables:

LOGIN_MAX=7              # Max attempts per window
LOGIN_WINDOW=5           # Window in minutes
LOGIN_VIOLATION_SCORE=1  # Severity score

2. Registration Rate Limiting

File: api/server/middleware/limiters/registerLimiter.js

const registerLimiter = rateLimit({
  windowMs: REGISTER_WINDOW * 60 * 1000,  // Default: 60 minutes
  max: REGISTER_MAX,                       // Default: 5 attempts
  message: 'Too many registration attempts'
});

Configuration:

REGISTER_MAX=5
REGISTER_WINDOW=60  # 60 minutes

3. Message Rate Limiting

File: api/server/middleware/limiters/messageLimiters.js

Dual Strategy: messageLimiters.js#L15-L55

// IP-based limiting
if (LIMIT_MESSAGE_IP) {
  app.use('/api/messages', ipMessageLimiter);
}

// User-based limiting
if (LIMIT_MESSAGE_USER) {
  app.use('/api/messages', userMessageLimiter);
}

Configuration:

LIMIT_MESSAGE_IP=true
LIMIT_MESSAGE_USER=true
MESSAGE_MAX=40
MESSAGE_WINDOW=5  # minutes
MESSAGE_VIOLATION_SCORE=1

4. Ban System

File: api/cache/banViolation.js

Auto-Ban Logic

Trigger: banViolation.js#L25-L45

// Ban when violations reach multiples of BAN_INTERVAL
if (BAN_VIOLATIONS && violationCount % BAN_INTERVAL === 0) {
  await banUser({
    userId,
    ipAddress: req.ip,
    duration: BAN_DURATION
  });
}

Ban Enforcement

Middleware: api/server/middleware/checkBan.js

Check: checkBan.js#L15-L45

async function checkBan(req, res, next) {
  // Check user ban
  const userBan = await Ban.findOne({
    userId: req.user.id,
    expiresAt: { $gt: new Date() }
  });

  // Check IP ban
  const ipBan = await Ban.findOne({
    ipAddress: req.ip,
    expiresAt: { $gt: new Date() }
  });

  if (userBan || ipBan) {
    return res.status(403).json({
      message: 'Account temporarily banned',
      expiresAt: (userBan || ipBan).expiresAt
    });
  }

  next();
}

Configuration:

BAN_VIOLATIONS=true      # Enable auto-ban
BAN_INTERVAL=20          # Violations before ban
BAN_DURATION=600000      # 10 minutes in ms

5. Other Rate Limiters

File Upload: api/server/middleware/limiters/uploadLimiters.js

  • Per-IP and per-user limits
  • Separate limits for different file types

Password Reset: api/server/middleware/limiters/resetPasswordLimiter.js

  • Prevents brute-force password reset attacks

Email Verification: api/server/middleware/limiters/verifyEmailLimiter.js

  • Rate limits verification attempts

Tool Calls: api/server/middleware/limiters/toolCallLimiter.js

  • Prevents excessive tool/plugin usage

TTS/STT: api/server/middleware/limiters/ttsLimiters.js

  • Rate limits speech synthesis/recognition

Privacy & Data Protection

1. User Data Collection

Registration Data: api/server/services/AuthService.js#L245-L270

const user = await User.create({
  email,
  password: hashedPassword,
  name,
  username,
  provider: 'local',
  role: isFirstUser ? 'ADMIN' : 'USER',
  emailVerified: false
});

OAuth Data: api/strategies/googleStrategy.js#L35-L55

// Only essential OAuth profile data
{
  email: profile.emails[0].value,
  name: profile.displayName,
  username: profile.username || email,
  picture: profile.photos[0].value,
  emailVerified: profile.emails[0].verified,
  provider: 'google'
}

2. PII Protection

Field Exclusion

User Queries: api/server/controllers/UserController.js#L25

const user = await User.findById(userId)
  .select('-password -totpSecret -backupCodes -__v');

Excluded Fields:

  • password - Never returned in responses
  • totpSecret - 2FA secret
  • backupCodes - Hashed backup codes
  • __v - MongoDB version key

Sensitive Data Redaction

Logging: api/config/parsers.js#L8-L15

const redactionPatterns = [
  /sk-[^\s]+/g,              // OpenAI keys
  /Bearer\s+[^\s]+/gi,       // Bearer tokens
  /apikey[=:]\s*[^\s&]+/gi, // API keys
  /key=[^&\s]+/gi           // Query keys
];

function redactSensitiveData(text) {
  return redactionPatterns.reduce(
    (str, pattern) => str.replace(pattern, '[REDACTED]'),
    text
  );
}

3. Terms of Service & Privacy Policy

File: librechat.example.yaml

Configuration:

# Terms of Service
termsOfService:
  modalAcceptance: true
  modalTitle: "Terms of Service"
  modalContent: |
    # Terms of Service
    By using this service, you agree to...

# Privacy Policy
privacyPolicy:
  externalUrl: "https://example.com/privacy"
  openNewTab: true

Acceptance Tracking: api/server/controllers/UserController.js#L185-L205

async acceptTerms(req, res) {
  const user = await User.findByIdAndUpdate(
    req.user.id,
    { termsAccepted: true, termsAcceptedAt: new Date() },
    { new: true }
  );

  res.json({ termsAccepted: true });
}

4. Email Domain Restrictions

Configuration: librechat.example.yaml#L45-L55

registration:
  allowedDomains:
    - "example.com"
    - "company.com"

Validation: Domain checked during registration Purpose: Restrict registrations to authorized domains

5. Consent Management

Email Consent

  • Transactional emails only (verification, password reset)
  • No marketing emails without explicit opt-in
  • Email service configurable

Cookie Consent

  • Essential cookies only (authentication, session)
  • No tracking cookies
  • HttpOnly, Secure, SameSite flags set

GDPR Compliance

1. Right to be Forgotten

File: api/server/controllers/UserController.js

Complete Data Deletion: UserController.js#L45-L125

async deleteUserController(req, res) {
  const userId = req.user.id;

  // 1. Delete all user messages
  await Message.deleteMany({ user: userId });

  // 2. Delete all sessions
  await Session.deleteMany({ userId });

  // 3. Delete transactions
  await Transaction.deleteMany({ user: userId });

  // 4. Delete API keys
  await UserKey.deleteMany({ userId });

  // 5. Delete balance records
  await Balance.deleteMany({ user: userId });

  // 6. Delete presets
  await Preset.deleteMany({ user: userId });

  // 7. Delete conversations
  await Conversation.deleteMany({ user: userId });

  // 8. Delete plugin auth
  await PluginAuth.deleteMany({ userId });

  // 9. Delete shared links
  await SharedLink.deleteMany({ userId });

  // 10. Delete files from storage
  await deleteUserFiles(userId);

  // 11. Delete file records
  await File.deleteMany({ userId });

  // 12. Delete tool calls
  await ToolCall.deleteMany({ userId });

  // 13. Delete agents
  await Agent.deleteMany({ author: userId });

  // 14. Delete assistants
  await Assistant.deleteMany({ author: userId });

  // 15. Delete conversation tags
  await ConversationTag.deleteMany({ user: userId });

  // 16. Delete memory entries
  await Memory.deleteMany({ userId });

  // 17. Delete prompts
  await Prompt.deleteMany({ author: userId });

  // 18. Delete actions
  await Action.deleteMany({ userId });

  // 19. Delete OAuth tokens
  await OAuthToken.deleteMany({ userId });

  // 20. Remove from groups
  await Group.updateMany(
    { members: userId },
    { $pull: { members: userId } }
  );

  // 21. Delete ACL entries
  await AccessControl.deleteMany({ principalId: userId });

  // 22. Delete user account
  await User.deleteOne({ _id: userId });

  // 23. Log deletion
  logger.info(`User deleted`, {
    email: req.user.email,
    id: userId
  });

  res.json({ message: 'Account and all data deleted' });
}

2. Right to Data Portability

User Data Export: api/server/controllers/UserController.js#L135-L165

async getUserData(req, res) {
  const user = await User.findById(req.user.id)
    .select('-password -totpSecret -backupCodes');

  const conversations = await Conversation.find({ user: req.user.id });
  const messages = await Message.find({ user: req.user.id });
  const agents = await Agent.find({ author: req.user.id });
  const prompts = await Prompt.find({ author: req.user.id });

  res.json({
    user,
    conversations,
    messages,
    agents,
    prompts,
    exportedAt: new Date()
  });
}

3. Right to Rectification

Profile Update: api/server/controllers/UserController.js#L170-L185

async updateUserProfile(req, res) {
  const allowedUpdates = ['name', 'username', 'avatar'];
  const updates = Object.keys(req.body)
    .filter(key => allowedUpdates.includes(key))
    .reduce((obj, key) => {
      obj[key] = req.body[key];
      return obj;
    }, {});

  const user = await User.findByIdAndUpdate(
    req.user.id,
    updates,
    { new: true, runValidators: true }
  ).select('-password -totpSecret -backupCodes');

  res.json(user);
}

4. Data Retention

Token Expiration:

// Email verification: 15 minutes
expiresIn: 900

// Password reset: 15 minutes
expiresIn: 900

// Access tokens: 15 minutes (configurable)
SESSION_EXPIRY: 900

// Refresh tokens: 7 days (configurable)
REFRESH_TOKEN_EXPIRY: 604800

// Invite tokens: 7 days
expiresIn: 604800

Automatic Cleanup:

  • MongoDB TTL indexes on expiring documents
  • Session cleanup on logout
  • Expired token cleanup via TTL

5. Data Processing Transparency

What Data is Collected:

  1. Account Data: Email, name, username, password (hashed)
  2. Usage Data: Conversations, messages, token usage
  3. Authentication Data: Sessions, OAuth tokens
  4. Preferences: Settings, model selections
  5. Security Data: Login attempts, violations, bans

What Data is NOT Collected:

  • Browsing history
  • Third-party analytics (unless GTM explicitly configured)
  • Marketing data
  • Geolocation (except IP for rate limiting)

6. Data Sharing

Third-Party Sharing:

// No data shared with third parties except:
// 1. AI providers (OpenAI, Anthropic, etc.) - only messages
// 2. OAuth providers (for authentication only)
// 3. Email service (for transactional emails only)
// 4. Storage providers (S3, Firebase) - only files

No Selling:

  • User data is never sold
  • No advertising partnerships
  • No data brokering

Security Configuration

Essential Environment Variables

# JWT Secrets (REQUIRED)
JWT_SECRET=your-strong-random-secret-here
JWT_REFRESH_SECRET=another-strong-random-secret

# Encryption Keys (REQUIRED)
CREDS_KEY=64_character_hex_string  # 32 bytes for AES-256
CREDS_IV=32_character_hex_string   # 16 bytes (legacy only)

# Session Configuration
SESSION_EXPIRY=900          # 15 minutes
REFRESH_TOKEN_EXPIRY=604800 # 7 days

# Password Policy
MIN_PASSWORD_LENGTH=8

# Rate Limiting
LOGIN_MAX=7
LOGIN_WINDOW=5
REGISTER_MAX=5
REGISTER_WINDOW=60
MESSAGE_MAX=40
MESSAGE_WINDOW=5

# Auto-Ban
BAN_VIOLATIONS=true
BAN_INTERVAL=20
BAN_DURATION=600000  # 10 minutes

# Security Features
ALLOW_PASSWORD_RESET=false
ALLOW_EMAIL_LOGIN=true
ALLOW_UNVERIFIED_EMAIL_LOGIN=false
ALLOW_SOCIAL_LOGIN=true

# Trust Proxy (for reverse proxies)
TRUST_PROXY=1

# Email (for verification and password reset)
EMAIL_SERVICE=gmail
[email protected]
EMAIL_PASSWORD=your-app-password
[email protected]

Security Headers

Recommended Headers (not all implemented):

// Implement in production:
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

SSL/TLS Configuration

Production:

  • HTTPS enforced via secure: true cookies
  • TLS 1.2+ recommended
  • Strong cipher suites
  • HSTS header (via Helmet)

Development:

  • HTTP allowed
  • secure: false cookies
  • Self-signed certificates acceptable

Security Assessment

✅ Strengths

  1. Multi-Factor Authentication

    • 7 authentication methods
    • TOTP-based 2FA with backup codes
    • Secure token generation
  2. Modern Encryption

    • AES-256-CTR (recommended v3)
    • Random IV per encryption
    • Secure key management
  3. Comprehensive Rate Limiting

    • 10+ specialized rate limiters
    • Auto-ban system
    • Violation tracking
  4. GDPR Compliance

    • Complete data deletion
    • Data export capability
    • Terms of service tracking
  5. Input Validation

    • Zod schema validation
    • MongoDB sanitization
    • Injection detection
  6. Session Security

    • Short-lived access tokens
    • Secure refresh tokens
    • HttpOnly, Secure, SameSite cookies
  7. RBAC & Permissions

    • Role-based access control
    • Granular permissions
    • ACL system

⚠️ Areas for Enhancement

  1. CSRF Protection

    • Current: Relies on SameSite cookies
    • Recommendation: Add explicit CSRF tokens for state-changing operations
  2. Security Headers

    • Missing: Content-Security-Policy (CSP)
    • Missing: X-Frame-Options
    • Missing: X-Content-Type-Options
    • Recommendation: Implement Helmet.js with full configuration
  3. Rate Limit Configuration

    • Current: Static configuration
    • Recommendation: Dynamic rate limits based on user reputation
  4. Audit Logging

    • Current: Basic logging
    • Recommendation: Comprehensive audit trail for sensitive operations
  5. Password Reset

    • Current: Disabled by default (good!)
    • Recommendation: Add additional verification steps when enabled
  6. API Key Rotation

    • Current: Manual rotation only
    • Recommendation: Automated key rotation policies
  7. Penetration Testing

    • Recommendation: Regular security audits
    • Recommendation: Bug bounty program

🔒 Security Best Practices Implemented

  • ✅ Password hashing with bcrypt (salt rounds = 10)
  • ✅ Token-based authentication (JWT)
  • ✅ Secure session management
  • ✅ Input validation and sanitization
  • ✅ Rate limiting on sensitive endpoints
  • ✅ MongoDB injection prevention
  • ✅ XSS prevention via HttpOnly cookies
  • ✅ Encrypted credential storage
  • ✅ HTTPS support in production
  • ✅ Secure file upload handling
  • ✅ OAuth 2.0 implementation
  • ✅ Two-factor authentication
  • ✅ RBAC and permissions
  • ✅ Auto-ban for violations

Summary

LibreChat demonstrates a strong security posture with:

Authentication

  • 7 authentication methods (Local, JWT, OAuth, LDAP, SAML, OpenID, 2FA)
  • Modern cryptographic implementations
  • Secure token lifecycle management

Authorization

  • Role-based access control (RBAC)
  • Granular permission system
  • Access control lists (ACL)

Data Protection

  • AES-256-CTR encryption for credentials
  • bcrypt password hashing
  • HttpOnly, Secure, SameSite cookies
  • PII protection and redaction

Security Features

  • Comprehensive rate limiting
  • Auto-ban system for violations
  • Input validation with injection prevention
  • Secure file upload handling
  • Session management with token rotation

Privacy & Compliance

  • GDPR-compliant data deletion
  • Data export capability
  • Terms of service tracking
  • Minimal data collection
  • No third-party data sharing (except necessary services)

Recommended Improvements

  1. Add explicit CSRF tokens
  2. Implement full CSP headers
  3. Add comprehensive audit logging
  4. Enable automated key rotation
  5. Conduct regular security audits

Overall Security Rating: ⭐⭐⭐⭐☆ (4/5)

  • Strong foundation with modern practices
  • Minor enhancements needed for enterprise-grade security
  • Excellent privacy and GDPR compliance

Last Updated: 2025-11-24 Security Contact: See SECURITY.md Vulnerability Reporting: GitHub Security Advisories

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment