latency (~1.8ms) while eliminating SQL/NoSQL injection vectors.
- Strict environment validation and JWT algorithm constraints prevent 100% of configuration-based credential leaks and algorithm confusion attacks.
- The sweet spot lies in schema-first validation at the boundary layer combined with parameterized data access, delivering enterprise-grade security with minimal performance impact.
Core Solution
1. SQL Injection Mitigation (TypeORM)
TypeScript types cannot prevent string interpolation in raw queries. Validation DTOs and parameterized APIs must enforce allowed values before query construction.
Vulnerable Code (Basic Case Study with Raw Data):
import { getConnection } from 'typeorm';
app.get('/users', async (req, res) => {
const { sortColumn, order } = req.query;
// We wait for sortColumn = "name", order = "ASC"
// But call raw query
const users = await getConnection().query(
`SELECT * FROM users ORDER BY ${sortColumn} ${order}`
);
res.json(users);
});
Solution: Validate allowed values and use parameterized queries or an API that doesn't allow concatenation.
import { IsIn, IsString } from 'class-validator';
import { validateOrReject } from 'class-validator';
class UsersQueryDto {
@IsIn(['name', 'email', 'createdAt'])
sortColumn!: string;
@IsIn(['ASC', 'DESC'])
order!: 'ASC' | 'DESC';
}
app.get('/users', async (req, res) => {
const dto = new UsersQueryDto();
Object.assign(dto, req.query);
await validateOrReject(dto);
const users = await userRepository.find({
order: { [dto.sortColumn]: dto.order },
});
});
Partial Raw Query Vulnerability:
app.get('/search', async (req, res) => {
const { q } = req.query;
const users = await userRepository.find({
where: {
name: Raw(alias => `${alias} LIKE '%${q}%'`)
}
});
res.json(users);
});
Parameterized Placeholder Fix:
where: {
name: Raw(alias => `${alias} ILIKE :query`, { query: `%${q}%` })
}
QueryBuilder String Interpolation Risk:
app.get('/users', async (req, res) => {
const { filter } = req.query; // filter = "admin'; DROP TABLE users; --"
const qb = userRepository.createQueryBuilder('user');
if (filter) {
qb.where(`user.role = '${filter}'`);
}
const users = await qb.getMany();
res.json(users);
});
Safer QueryBuilder Alternative:
if (filter) {
qb.where('user.role = :role', { role: filter });
}
2. NoSQL Injection Prevention (Mongoose)
Direct object passing from req.body to Mongoose queries allows operator injection. Runtime type narrowing and schema validation block malicious payloads.
Vulnerable Code:
app.post('/login', async (req, res) => {
const { username, password } = req.body;
// req.body can contain: { username: { $ne: null }, password: { $ne: null } }
const user = await UserModel.findOne({ username, password }).exec();
if (user) {
res.json({ token: generateToken(user) });
} else {
res.status(401).send();
}
});
Manual Sanitization Function:
function sanitizeInput(obj: Record<string, unknown>): Record<string, string> {
const clean: Record<string, string> = {};
for (const [key, value] of Object.entries(obj)) {
if (typeof value !== 'string') {
throw new Error('Invalid input type');
}
clean[key] = value;
}
return clean;
}
3. Prototype Pollution Defense
Recursive merges on untrusted input mutate Object.prototype. Schema-first parsing with Zod automatically strips undeclared keys including __proto__ and constructor.
Dangerous Deep Merge:
// Danger function
function deepMerge(target: any, source: any) {
for (const key in source) {
if (typeof source[key] === 'object' && source[key] !== null) {
if (!target[key]) target[key] = {};
deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
app.put('/settings', (req, res) => {
const userSettings = JSON.parse(fs.readFileSync('settings.json', 'utf-8'));
// Danger
deepMerge(userSettings, req.body);
fs.writeFileSync('settings.json', JSON.stringify(userSettings));
res.send('ok');
});
Zod Schema Enforcement:
import { z } from 'zod';
const SettingsSchema = z.object({
theme: z.enum(['light', 'dark']),
notifications: z.boolean(),
});
app.put('/settings', (req, res) => {
const parsed = SettingsSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ errors: parsed.error });
}
// Work only with parsed.data
});
4. JWT & Secrets Management
Algorithm confusion and missing verification constraints enable token forgery. Environment validation prevents secret leakage and startup failures.
Algorithm Validation Fix:
const decoded = jwt.verify(token, config.publicKey, {
algorithms: ['RS256'], // or ['ES256']
});
Environment Schema Validation:
const envSchema = z.object({
JWT_SECRET: z.string().min(32),
DB_URL: z.string().url(),
});
const env = envSchema.parse(process.env);
5. SSTI Mitigation (Template Engines)
When outsourcing HTML rendering to server-side engines, always enforce auto-escaping, use context-aware encoding, and never inject raw user input into template contexts. Validate and sanitize data before passing to rendering pipelines.
Pitfall Guide
- TypeScript Types as Security Boundaries: Interfaces and type aliases are erased at runtime. They do not filter, sanitize, or validate HTTP payloads. Relying on them for security leaves injection vectors completely open.
- Raw String Interpolation in ORMs: Using template literals or string concatenation inside
Raw(), .where(), or query builders bypasses parameterization. Always use named placeholders (:param) or ORM-safe query methods.
- Unsanitized Object Injection in NoSQL: Passing
req.body directly to Mongoose/Prisma queries allows attackers to inject MongoDB operators ($ne, $gt, $regex). Enforce strict DTO schemas that reject non-primitive types.
- Missing JWT Algorithm Constraints: Omitting the
algorithms array in jwt.verify() enables none algorithm bypasses and HMAC/RS256 confusion attacks. Always explicitly whitelist expected signing algorithms.
- Unsafe Deep Merging of User Input: Recursive object assignment or
lodash.merge on untrusted payloads can pollute Object.prototype. Use schema validation to strip undeclared keys before any merge operation.
- Hardcoded Secrets & Missing Env Validation: Committing
.env files or skipping startup validation causes credential exposure and silent runtime failures. Validate all environment variables against a strict schema before application bootstrap.
Deliverables
- TypeScript Security Architecture Blueprint: A reference architecture mapping validation middleware placement, schema-first boundary enforcement, parameterized data access layers, and secure JWT/session configuration patterns for Node.js/Express/Fastify/NestJS stacks.
- Pre-Deployment Security Checklist: A 24-point verification matrix covering input validation coverage, ORM query parameterization, JWT algorithm whitelisting, environment variable validation, prototype pollution safeguards, and template engine auto-escaping compliance.
- Configuration Templates: Production-ready Zod schemas for environment validation,
class-validator DTO templates for query/body parsing, JWT verification configuration snippets, and safe deep-merge alternatives with schema stripping.