Skip to main content

Security

Current security status and required improvements for the TrickBook backend.

Status, 2026-10-06

This page is the July 2026 snapshot and several rows below are already fixed in code. Fixed since the September review: Apple sign-in identity is derived from the verified token (#65), avatar upload requires auth and targets the caller (#65), request bodies and identity tokens are no longer logged (#65, TrickBookWebsite #89), a /health probe exists (#65), collection validators run in warn mode (#65), and Stripe webhook deliveries verify on the raw body with idempotent handling (#75). Still open: the token check on the voice WebSocket upgrade (caps shipped 2026-09-30), the credential rotations from the September review, dependency upgrades (helmet 3, jsonwebtoken 8, joi 14), and Sentry. A rewrite of this page is tracked as the docs truth pass in Progress: September 24 to October 6.

Critical Issues​

Immediate Action Required

The following issues must be addressed before any further development.

1. Exposed Credentials​

Status: CRITICAL

The .env file contains plaintext credentials that may be committed to git:

# EXPOSED - Rotate these immediately
ATLAS_URI=mongodb+srv://[REDACTED]@cluster.mongodb.net/...
AWS_KEY=[REDACTED]
AWS_SECRET=[REDACTED]
EMAIL_PASSWORD=[REDACTED]

Fix:

  1. Rotate ALL credentials immediately
  2. Add .env to .gitignore
  3. Use secrets manager (AWS Secrets Manager, Doppler, etc.)
  4. Create .env.example with placeholder values

2. Hardcoded JWT Secret​

Status: CRITICAL

// Current code - INSECURE
jwt.sign(payload, "jwtPrivateKey");

Fix:

jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1h' });

3. No Token Expiration​

Status: CRITICAL

JWT tokens never expire, meaning a leaked token grants permanent access.

Fix:

const token = jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: '1h' // Short-lived access token
});

// Implement refresh tokens for better UX
const refreshToken = jwt.sign(
{ _id: user._id },
process.env.REFRESH_SECRET,
{ expiresIn: '7d' }
);

4. End-of-Life Node.js​

Status: CRITICAL

Node.js 12.6.x reached end-of-life in April 2022. No security patches available.

Fix:

// package.json
{
"engines": {
"node": ">=18.0.0"
}
}

High Priority Issues​

5. Outdated helmet​

Current: 3.22.0 (2019) Latest: 7.x

Missing security headers and protections.

Fix:

npm install helmet@latest
const helmet = require('helmet');
app.use(helmet());

6. No Rate Limiting​

Login and API endpoints have no protection against brute force attacks.

Fix:

npm install express-rate-limit
const rateLimit = require('express-rate-limit');

// Strict limit for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5,
message: 'Too many login attempts, try again later'
});

router.post('/auth', authLimiter, loginHandler);

// General API limit
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100
});

app.use('/api/', apiLimiter);

7. CORS Wildcard​

Current:

app.use(cors()); // Allows all origins

Fix:

const corsOptions = {
origin: [
'https://thetrickbook.com',
'https://www.thetrickbook.com',
'https://admin.thetrickbook.com'
],
credentials: true
};

app.use(cors(corsOptions));

8. Unrestricted Public Endpoints​

These endpoints expose data without authentication:

EndpointRisk
GET /api/users/allExposes all user emails
GET /api/listings/allExposes all user data
GET /api/spotsMay be intentional

Fix: Add authentication or remove if not needed.


Medium Priority Issues​

9. Weak Password Requirements​

Current:

password: Joi.string().min(5).required()

Fix:

password: Joi.string()
.min(8)
.pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.required()
.messages({
'string.pattern.base': 'Password must contain uppercase, lowercase, and number'
})

10. No Input Sanitization​

User input used directly in regex patterns:

// Vulnerable
{ name: { $regex: userInput, $options: "i" } }

Fix:

const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

{ name: { $regex: escapeRegex(userInput), $options: "i" } }

11. Missing HTTPS Redirect​

Ensure all traffic uses HTTPS:

app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
return res.redirect(`https://${req.header('host')}${req.url}`);
}
next();
});

12. No Request Size Limits​

Large payloads could cause DoS:

app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ limit: '10kb', extended: true }));

Security Checklist​

Authentication​

  • Move JWT secret to environment variable
  • Add token expiration (1 hour)
  • Implement refresh tokens
  • Add password complexity requirements
  • Add account lockout after failed attempts

Authorization​

  • Review all public endpoints
  • Add authentication to /api/users/all
  • Verify admin checks on all admin routes

Network​

  • Update CORS whitelist
  • Add rate limiting
  • Enforce HTTPS
  • Add request size limits

Dependencies​

  • Upgrade Node.js to 18+
  • Update helmet to 7.x
  • Update jsonwebtoken to 9.x
  • Update joi to 17.x
  • Run npm audit and fix vulnerabilities

Credentials​

  • Rotate MongoDB password
  • Rotate AWS access keys
  • Rotate email password
  • Create new JWT secret
  • Add .env to .gitignore
  • Set up secrets manager

Monitoring​

  • Add error logging (Winston/Pino)
  • Set up Sentry for error tracking
  • Monitor for suspicious activity
  • Set up alerts for failed auth attempts

Secure Configuration Template​

// index.js - Secure setup

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

const app = express();

// Security headers
app.use(helmet());

// CORS whitelist
app.use(cors({
origin: ['https://thetrickbook.com'],
credentials: true
}));

// Rate limiting
app.use(rateLimit({
windowMs: 60 * 1000,
max: 100
}));

// Request size limits
app.use(express.json({ limit: '10kb' }));

// HTTPS redirect (behind proxy)
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
return res.redirect(301, `https://${req.header('host')}${req.url}`);
}
next();
});

// Validate required env vars
const required = ['ATLAS_URI', 'JWT_SECRET', 'AWS_KEY'];
required.forEach(key => {
if (!process.env[key]) {
console.error(`Missing required env var: ${key}`);
process.exit(1);
}
});