Security of TypeScript Applications: Top 10 Vulnerabilities in Practice
TypeScript is a superset of JavaScript that adds static typing. Many developers mistakenly believe that strict typing automatically makes an application secure. This is not the case. The TypeScript compiler checks types, but does not protect against logical errors, injections, or authentication issues. In this article, we will analyze the 10 most common vulnerabilities in TypeScript applications, show code examples, and provide specific recommendations for protection. The article will be useful for both beginners and experienced developers.
1. Cross-Site Scripting (XSS) via Improper Input Handling
XSS remains one of the most dangerous vulnerabilities. TypeScript does not automatically escape output. If you insert user data into an HTML template without sanitization, you open the door to attacks.
Vulnerable Code
function renderComment(comment: string): string { return `<div>${comment}</div>`;}
// Attacker passes: <script>alert('XSS')</script>Fixed Code
import { escape } from 'lodash';
function renderComment(comment: string): string { const safeComment = escape(comment); return `<div>${safeComment}</div>`;}Recommendation: Always use libraries for escaping (DOMPurify, lodash.escape) or template engines with auto-escaping (React JSX, Handlebars).
2. SQL Injection via Template Strings
TypeScript allows using template strings to build SQL queries. This is a direct path to SQL injection if you substitute user data.
Vulnerable Code
async function getUser(id: string): Promise<User | null> { const query = `SELECT * FROM users WHERE id = '${id}'`; const result = await db.query(query); return result.rows[0];}Fixed Code
async function getUser(id: string): Promise<User | null> { const query = 'SELECT * FROM users WHERE id = $1'; const result = await db.query(query, [id]); return result.rows[0];}Recommendation: Use parameterized queries or ORM (TypeORM, Prisma, Sequelize). Never concatenate strings for SQL.
3. Insecure Password Comparison
An error in implementing password comparison can lead to timing attacks. TypeScript developers often use the === operator to compare hashes, which is insecure.
Vulnerable Code
async function verifyPassword( inputPassword: string, storedHash: string): Promise<boolean> { const inputHash = await bcrypt.hash(inputPassword, 10); return inputHash === storedHash; // Vulnerability: timing attack}Fixed Code
import bcrypt from 'bcrypt';
async function verifyPassword( inputPassword: string, storedHash: string): Promise<boolean> { return bcrypt.compare(inputPassword, storedHash);}Recommendation: Use built-in comparison functions from hashing libraries (bcrypt.compare, argon2.verify). They are protected against timing attacks.
4. Prototype Pollution via Recursive Object Merging
TypeScript applications often use functions for deep copying or merging objects. If keys are not controlled, an attacker can pollute the Object.prototype.
Vulnerable Code
function merge(target: Record<string, any>, source: Record<string, any>) { for (const key in source) { if (typeof source[key] === 'object') { target[key] = merge(target[key] || {}, source[key]); } else { target[key] = source[key]; } } return target;}
// Attacker pe