Advanced JavaScriptFeatured
JavaScript Security: Best Practices and Common Vulnerabilities
Learn JavaScript security best practices. Understand common vulnerabilities like XSS, CSRF, and injection attacks, and how to prevent them.
By JavaScriptDoc Team•
securityjavascriptxsscsrfvulnerabilities
JavaScript Security: Best Practices and Common Vulnerabilities
Security is crucial in JavaScript applications. This guide covers common vulnerabilities, attack vectors, and best practices for building secure applications.
Cross-Site Scripting (XSS)
Understanding XSS Attacks
// Vulnerable code - DOM XSS
function displayUserInput(userInput) {
// DANGEROUS: Direct HTML injection
document.getElementById('output').innerHTML = userInput;
}
// Attack example
displayUserInput('<script>alert("XSS Attack!")</script>');
displayUserInput('<img src=x onerror="alert(\'XSS\')"/>');
// Vulnerable code - Reflected XSS
function displaySearchResults() {
// DANGEROUS: Using URL parameters directly
const query = new URLSearchParams(window.location.search).get('q');
document.getElementById('results').innerHTML = `
<h2>Search results for: ${query}</h2>
`;
}
// Attack URL: site.com/search?q=<script>alert('XSS')</script>
// Vulnerable code - Stored XSS
function displayComment(comment) {
// DANGEROUS: Displaying stored user content
const commentDiv = document.createElement('div');
commentDiv.innerHTML = comment.text;
document.getElementById('comments').appendChild(commentDiv);
}
XSS Prevention
// Safe HTML encoding
class HTMLEncoder {
static encode(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
static encodeAttribute(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
}
}
// Safe display functions
function safeDisplayUserInput(userInput) {
// Use textContent instead of innerHTML
document.getElementById('output').textContent = userInput;
}
function safeDisplayHTML(userInput) {
// Sanitize HTML if needed
const sanitized = DOMPurify.sanitize(userInput);
document.getElementById('output').innerHTML = sanitized;
}
// Content Security Policy (CSP) headers
// Add to server response headers:
// Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-random123';
// Safe template rendering
class SafeTemplate {
static render(template, data) {
// Use a template engine with auto-escaping
return template.replace(/{{(\w+)}}/g, (match, key) => {
return HTMLEncoder.encode(data[key] || '');
});
}
}
// React/Vue automatically escape content
function SafeComponent({ userInput }) {
// This is automatically escaped
return <div>{userInput}</div>;
}
// Validate and sanitize input
class InputValidator {
static sanitizeHTML(input) {
// Whitelist allowed tags and attributes
const allowedTags = ['p', 'br', 'strong', 'em', 'u'];
const allowedAttributes = [];
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: allowedTags,
ALLOWED_ATTR: allowedAttributes,
});
}
static isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
static stripScripts(input) {
return input.replace(
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
''
);
}
}
Cross-Site Request Forgery (CSRF)
Understanding CSRF Attacks
// Vulnerable code - No CSRF protection
app.post('/api/transfer', async (req, res) => {
const { toAccount, amount } = req.body;
const userId = req.session.userId;
// DANGEROUS: No CSRF token validation
await transferMoney(userId, toAccount, amount);
res.json({ success: true });
});
// Attack example (malicious site)
// <form action="https://bank.com/api/transfer" method="POST">
// <input type="hidden" name="toAccount" value="attacker" />
// <input type="hidden" name="amount" value="1000" />
// </form>
// <script>document.forms[0].submit();</script>
CSRF Prevention
// CSRF token generation and validation
class CSRFProtection {
static generateToken() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(
''
);
}
static validateToken(sessionToken, requestToken) {
if (!sessionToken || !requestToken) {
return false;
}
// Constant-time comparison to prevent timing attacks
return this.constantTimeCompare(sessionToken, requestToken);
}
static constantTimeCompare(a, b) {
if (a.length !== b.length) {
return false;
}
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return result === 0;
}
}
// Server-side implementation
app.use((req, res, next) => {
if (!req.session.csrfToken) {
req.session.csrfToken = CSRFProtection.generateToken();
}
res.locals.csrfToken = req.session.csrfToken;
next();
});
// Protected endpoint
app.post('/api/transfer', (req, res) => {
const sessionToken = req.session.csrfToken;
const requestToken = req.body.csrfToken || req.headers['x-csrf-token'];
if (!CSRFProtection.validateToken(sessionToken, requestToken)) {
return res.status(403).json({ error: 'Invalid CSRF token' });
}
// Process the request
});
// Client-side implementation
class SecureAPI {
constructor() {
this.csrfToken = this.getCSRFToken();
}
getCSRFToken() {
// Get from meta tag or cookie
const metaTag = document.querySelector('meta[name="csrf-token"]');
return metaTag ? metaTag.content : null;
}
async post(url, data) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': this.csrfToken,
},
credentials: 'same-origin',
body: JSON.stringify(data),
});
return response.json();
}
}
// Double submit cookie pattern
class DoubleSubmitCSRF {
static setToken() {
const token = CSRFProtection.generateToken();
// Set as HTTP-only cookie
document.cookie = `csrf-token=${token}; Path=/; SameSite=Strict`;
// Also store in sessionStorage for requests
sessionStorage.setItem('csrf-token', token);
return token;
}
static getToken() {
return sessionStorage.getItem('csrf-token');
}
}
Input Validation and Sanitization
Comprehensive Input Validation
class InputSecurity {
// Prevent SQL injection in client-side validation
static sanitizeForSQL(input) {
// Note: Real SQL injection prevention should be done server-side
// This is just for client-side pre-validation
return input.replace(/['"\\]/g, '\\$&').replace(/[\x00-\x1f\x7f]/g, '');
}
// Prevent NoSQL injection
static sanitizeForMongoDB(input) {
if (typeof input !== 'string') {
throw new Error('Input must be a string');
}
// Remove operators
return input.replace(/[$]/g, '');
}
// File upload validation
static validateFile(file, options = {}) {
const {
maxSize = 5 * 1024 * 1024, // 5MB default
allowedTypes = ['image/jpeg', 'image/png', 'image/gif'],
allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif'],
} = options;
const errors = [];
// Check file size
if (file.size > maxSize) {
errors.push(`File size exceeds ${maxSize / 1024 / 1024}MB limit`);
}
// Check MIME type
if (!allowedTypes.includes(file.type)) {
errors.push(`File type ${file.type} is not allowed`);
}
// Check file extension
const extension = file.name
.substring(file.name.lastIndexOf('.'))
.toLowerCase();
if (!allowedExtensions.includes(extension)) {
errors.push(`File extension ${extension} is not allowed`);
}
// Check for double extensions
const parts = file.name.split('.');
if (parts.length > 2) {
errors.push('Files with multiple extensions are not allowed');
}
return {
valid: errors.length === 0,
errors,
};
}
// URL validation
static validateURL(url, options = {}) {
const {
allowedProtocols = ['https:', 'http:'],
allowedDomains = null,
blockLocalhost = true,
} = options;
try {
const parsed = new URL(url);
// Check protocol
if (!allowedProtocols.includes(parsed.protocol)) {
throw new Error(`Protocol ${parsed.protocol} is not allowed`);
}
// Check for localhost/internal IPs
if (blockLocalhost) {
const hostname = parsed.hostname;
if (
hostname === 'localhost' ||
hostname === '127.0.0.1' ||
hostname.startsWith('192.168.') ||
hostname.startsWith('10.') ||
hostname.startsWith('172.')
) {
throw new Error('Local/internal URLs are not allowed');
}
}
// Check allowed domains
if (allowedDomains && !allowedDomains.includes(parsed.hostname)) {
throw new Error(`Domain ${parsed.hostname} is not allowed`);
}
return { valid: true, parsed };
} catch (error) {
return { valid: false, error: error.message };
}
}
}
// Form validation with security in mind
class SecureFormValidator {
constructor(form) {
this.form = form;
this.rules = new Map();
}
addRule(fieldName, validators) {
this.rules.set(fieldName, validators);
}
validate() {
const errors = {};
for (const [fieldName, validators] of this.rules) {
const field = this.form.elements[fieldName];
if (!field) continue;
const value = field.value;
const fieldErrors = [];
for (const validator of validators) {
const result = validator(value);
if (result !== true) {
fieldErrors.push(result);
}
}
if (fieldErrors.length > 0) {
errors[fieldName] = fieldErrors;
}
}
return {
valid: Object.keys(errors).length === 0,
errors,
};
}
}
// Common validators
const Validators = {
required: (value) => value.trim() !== '' || 'This field is required',
email: (value) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(value) || 'Invalid email address';
},
minLength: (min) => (value) =>
value.length >= min || `Must be at least ${min} characters`,
maxLength: (max) => (value) =>
value.length <= max || `Must be no more than ${max} characters`,
pattern: (regex, message) => (value) => regex.test(value) || message,
noScriptTags: (value) =>
!/<script[^>]*>.*?<\/script>/gi.test(value) ||
'Script tags are not allowed',
alphanumeric: (value) =>
/^[a-zA-Z0-9]+$/.test(value) || 'Only letters and numbers are allowed',
strongPassword: (value) => {
const checks = [
{ regex: /.{8,}/, message: 'At least 8 characters' },
{ regex: /[A-Z]/, message: 'At least one uppercase letter' },
{ regex: /[a-z]/, message: 'At least one lowercase letter' },
{ regex: /[0-9]/, message: 'At least one number' },
{ regex: /[^A-Za-z0-9]/, message: 'At least one special character' },
];
for (const check of checks) {
if (!check.regex.test(value)) {
return check.message;
}
}
return true;
},
};
Authentication and Authorization
Secure Authentication
// Client-side authentication helper
class AuthManager {
constructor() {
this.tokenKey = 'auth_token';
this.refreshTokenKey = 'refresh_token';
}
// Secure token storage
setTokens(accessToken, refreshToken) {
// Store in memory for most secure (but lost on refresh)
this.accessToken = accessToken;
// Store refresh token in httpOnly cookie (server-side)
// Or use sessionStorage for less sensitive apps
sessionStorage.setItem(this.refreshTokenKey, refreshToken);
}
getAccessToken() {
return this.accessToken;
}
async refreshAccessToken() {
const refreshToken = sessionStorage.getItem(this.refreshTokenKey);
if (!refreshToken) {
throw new Error('No refresh token available');
}
const response = await fetch('/api/refresh', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) {
throw new Error('Failed to refresh token');
}
const { accessToken } = await response.json();
this.accessToken = accessToken;
return accessToken;
}
logout() {
this.accessToken = null;
sessionStorage.removeItem(this.refreshTokenKey);
// Also call server to invalidate tokens
}
// Add token to requests
async authenticatedFetch(url, options = {}) {
let token = this.getAccessToken();
if (!token) {
token = await this.refreshAccessToken();
}
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
}
}
// Password hashing (client-side, before sending to server)
// Note: Server should still hash passwords
class PasswordSecurity {
static async hashPassword(password, salt) {
const encoder = new TextEncoder();
const data = encoder.encode(password + salt);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
}
static generateSalt() {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('');
}
static checkPasswordStrength(password) {
let strength = 0;
const checks = [
{ regex: /.{8,}/, value: 1 },
{ regex: /.{12,}/, value: 1 },
{ regex: /[A-Z]/, value: 1 },
{ regex: /[a-z]/, value: 1 },
{ regex: /[0-9]/, value: 1 },
{ regex: /[^A-Za-z0-9]/, value: 1 },
];
checks.forEach((check) => {
if (check.regex.test(password)) {
strength += check.value;
}
});
return {
score: strength,
level: strength < 3 ? 'weak' : strength < 5 ? 'medium' : 'strong',
};
}
}
// Role-based access control
class RBAC {
constructor(userRoles) {
this.userRoles = userRoles;
this.permissions = new Map();
}
defineRole(role, permissions) {
this.permissions.set(role, new Set(permissions));
}
can(permission) {
for (const role of this.userRoles) {
const rolePermissions = this.permissions.get(role);
if (rolePermissions && rolePermissions.has(permission)) {
return true;
}
}
return false;
}
hasRole(role) {
return this.userRoles.includes(role);
}
checkAccess(requiredPermission) {
if (!this.can(requiredPermission)) {
throw new Error(
`Access denied: Missing permission '${requiredPermission}'`
);
}
}
}
Secure Communication
HTTPS and Secure Cookies
// Secure cookie management
class SecureCookies {
static set(name, value, options = {}) {
const defaults = {
secure: true, // HTTPS only
sameSite: 'Strict', // CSRF protection
httpOnly: false, // Can't set httpOnly from JS
path: '/',
expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 1 day
};
const settings = { ...defaults, ...options };
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (settings.expires) {
cookie += `; expires=${settings.expires.toUTCString()}`;
}
if (settings.path) {
cookie += `; path=${settings.path}`;
}
if (settings.domain) {
cookie += `; domain=${settings.domain}`;
}
if (settings.secure) {
cookie += '; secure';
}
if (settings.sameSite) {
cookie += `; samesite=${settings.sameSite}`;
}
document.cookie = cookie;
}
static get(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
return decodeURIComponent(parts.pop().split(';').shift());
}
return null;
}
static delete(name, path = '/') {
this.set(name, '', {
expires: new Date(0),
path,
});
}
}
// Encrypted local storage
class EncryptedStorage {
constructor(encryptionKey) {
this.encryptionKey = encryptionKey;
}
async encrypt(data) {
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(JSON.stringify(data));
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(this.encryptionKey),
{ name: 'AES-GCM' },
false,
['encrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
dataBuffer
);
return {
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted)),
};
}
async decrypt(encryptedData) {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(this.encryptionKey),
{ name: 'AES-GCM' },
false,
['decrypt']
);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: new Uint8Array(encryptedData.iv) },
key,
new Uint8Array(encryptedData.data)
);
return JSON.parse(decoder.decode(decrypted));
}
async setItem(key, value) {
const encrypted = await this.encrypt(value);
localStorage.setItem(key, JSON.stringify(encrypted));
}
async getItem(key) {
const stored = localStorage.getItem(key);
if (!stored) return null;
const encrypted = JSON.parse(stored);
return await this.decrypt(encrypted);
}
}
Content Security Policy
Implementing CSP
// CSP header generator
class ContentSecurityPolicy {
constructor() {
this.directives = new Map();
}
setDirective(directive, sources) {
this.directives.set(directive, sources);
}
generateNonce() {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return btoa(String.fromCharCode.apply(null, array));
}
toString() {
const policies = [];
for (const [directive, sources] of this.directives) {
const sourceList = Array.isArray(sources) ? sources.join(' ') : sources;
policies.push(`${directive} ${sourceList}`);
}
return policies.join('; ');
}
static recommended() {
const csp = new ContentSecurityPolicy();
csp.setDirective('default-src', ["'self'"]);
csp.setDirective('script-src', ["'self'", "'nonce-{NONCE}'"]);
csp.setDirective('style-src', ["'self'", "'unsafe-inline'"]);
csp.setDirective('img-src', ["'self'", 'data:', 'https:']);
csp.setDirective('font-src', ["'self'"]);
csp.setDirective('connect-src', ["'self'"]);
csp.setDirective('frame-ancestors', ["'none'"]);
csp.setDirective('base-uri', ["'self'"]);
csp.setDirective('form-action', ["'self'"]);
return csp;
}
}
// CSP violation reporter
class CSPReporter {
static setup() {
document.addEventListener('securitypolicyviolation', (event) => {
this.reportViolation({
blockedURI: event.blockedURI,
columnNumber: event.columnNumber,
disposition: event.disposition,
documentURI: event.documentURI,
effectiveDirective: event.effectiveDirective,
lineNumber: event.lineNumber,
originalPolicy: event.originalPolicy,
referrer: event.referrer,
sample: event.sample,
sourceFile: event.sourceFile,
statusCode: event.statusCode,
violatedDirective: event.violatedDirective,
});
});
}
static async reportViolation(violation) {
try {
await fetch('/api/csp-report', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
violation,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
}),
});
} catch (error) {
console.error('Failed to report CSP violation:', error);
}
}
}
// Dynamic script loading with nonce
class SecureScriptLoader {
constructor(nonce) {
this.nonce = nonce;
}
loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.nonce = this.nonce;
script.onload = () => resolve();
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
document.head.appendChild(script);
});
}
inlineScript(code) {
const script = document.createElement('script');
script.nonce = this.nonce;
script.textContent = code;
document.head.appendChild(script);
}
}
Third-Party Dependencies
Secure Dependency Management
// Subresource Integrity (SRI) checker
class SRIChecker {
static async generateHash(content, algorithm = 'sha384') {
const encoder = new TextEncoder();
const data = encoder.encode(content);
const hashBuffer = await crypto.subtle.digest(
algorithm.toUpperCase().replace('SHA', 'SHA-'),
data
);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashBase64 = btoa(String.fromCharCode(...hashArray));
return `${algorithm}-${hashBase64}`;
}
static async verifyScript(url, expectedHash) {
try {
const response = await fetch(url);
const content = await response.text();
const actualHash = await this.generateHash(content);
return actualHash === expectedHash;
} catch (error) {
console.error('Failed to verify script:', error);
return false;
}
}
static addScriptWithSRI(src, integrity, options = {}) {
const script = document.createElement('script');
script.src = src;
script.integrity = integrity;
script.crossOrigin = options.crossOrigin || 'anonymous';
if (options.async) script.async = true;
if (options.defer) script.defer = true;
return new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
document.head.appendChild(script);
});
}
}
// Dependency auditor
class DependencyAuditor {
constructor() {
this.vulnerabilityDatabase = new Map();
}
async checkDependency(name, version) {
// In real implementation, check against vulnerability databases
const vulnerabilities = this.vulnerabilityDatabase.get(name) || [];
return vulnerabilities.filter((vuln) => {
return this.versionInRange(version, vuln.affectedVersions);
});
}
versionInRange(version, range) {
// Simplified version checking
// In production, use a proper semver library
return range.includes(version);
}
async auditPackageJson(packageJson) {
const report = {
vulnerabilities: [],
summary: {
total: 0,
high: 0,
medium: 0,
low: 0,
},
};
const dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
};
for (const [name, version] of Object.entries(dependencies)) {
const vulns = await this.checkDependency(name, version);
vulns.forEach((vuln) => {
report.vulnerabilities.push({
package: name,
version,
vulnerability: vuln,
});
report.summary.total++;
report.summary[vuln.severity]++;
});
}
return report;
}
}
Security Headers
// Security headers manager
class SecurityHeaders {
static getRecommendedHeaders() {
return {
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'Content-Security-Policy': this.getCSP(),
};
}
static getCSP() {
return [
"default-src 'self'",
"script-src 'self' 'nonce-{NONCE}'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
].join('; ');
}
static validateHeaders(headers) {
const required = this.getRecommendedHeaders();
const missing = [];
const incorrect = [];
for (const [header, expectedValue] of Object.entries(required)) {
if (!headers[header]) {
missing.push(header);
} else if (
headers[header] !== expectedValue &&
header !== 'Content-Security-Policy'
) {
incorrect.push({
header,
expected: expectedValue,
actual: headers[header],
});
}
}
return { missing, incorrect };
}
}
Best Practices Summary
// Security checklist implementation
class SecurityChecklist {
static async runChecks() {
const results = [];
// Check for HTTPS
results.push({
check: 'HTTPS',
passed: location.protocol === 'https:',
message: 'Site should be served over HTTPS',
});
// Check for secure cookies
const cookies = document.cookie.split(';');
const insecureCookies = cookies.filter((cookie) => {
return !cookie.includes('Secure') || !cookie.includes('SameSite');
});
results.push({
check: 'Secure Cookies',
passed: insecureCookies.length === 0,
message: 'All cookies should have Secure and SameSite flags',
});
// Check for CSP
const cspHeader = document.querySelector(
'meta[http-equiv="Content-Security-Policy"]'
);
results.push({
check: 'Content Security Policy',
passed: cspHeader !== null,
message: 'CSP should be implemented',
});
// Check for external scripts without SRI
const externalScripts = document.querySelectorAll('script[src^="http"]');
const scriptsWithoutSRI = Array.from(externalScripts).filter(
(script) => !script.integrity
);
results.push({
check: 'Subresource Integrity',
passed: scriptsWithoutSRI.length === 0,
message: 'External scripts should use SRI',
});
return results;
}
}
// Security monitoring
class SecurityMonitor {
static init() {
// Monitor for XSS attempts
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
this.checkForDangerousContent(node);
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
// Monitor for clickjacking
if (window.self !== window.top) {
console.warn('Page is loaded in a frame - possible clickjacking');
// Optionally break out of frame
// window.top.location = window.self.location;
}
}
static checkForDangerousContent(element) {
// Check for inline scripts
if (element.tagName === 'SCRIPT' && !element.src) {
console.warn('Inline script detected:', element);
}
// Check for javascript: URLs
const dangerousAttributes = ['href', 'src', 'action'];
dangerousAttributes.forEach((attr) => {
const value = element.getAttribute(attr);
if (value && value.startsWith('javascript:')) {
console.error('Dangerous JavaScript URL detected:', element);
element.removeAttribute(attr);
}
});
// Check for event handlers
const eventHandlers = Array.from(element.attributes).filter((attr) =>
attr.name.startsWith('on')
);
if (eventHandlers.length > 0) {
console.warn('Inline event handlers detected:', element);
}
}
}
Conclusion
JavaScript security requires a multi-layered approach:
- Input validation and sanitization
- XSS prevention through proper encoding
- CSRF protection with tokens
- Secure authentication and authorization
- HTTPS and secure communication
- Content Security Policy implementation
- Dependency security management
Key takeaways:
- Never trust user input
- Always encode output
- Use security headers
- Keep dependencies updated
- Implement defense in depth
- Regular security audits
Stay vigilant and keep learning about emerging security threats!