# Security Implementation Guide Comprehensive security requirements and implementation patterns for agent templates. ## Security Principles ### 1. Defense in Depth - **Multiple layers of protection** at different levels - **Input validation** at client and server side - **Output encoding** for all dynamic content - **Access control** at every endpoint ### 2. Least Privilege - **Minimal permissions** for each component - **Restricted access** to sensitive data - **Limited functionality** exposure ### 3. Secure by Default - **Safe defaults** for all configurations - **Explicit security** rather than assumed - **Fail-safe mechanisms** when security fails ## Input Validation and Sanitization ### Client-Side Input Validation ```javascript // Input Sanitization Utilities const InputValidator = { // Basic HTML sanitization sanitizeHTML(input) { return input .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/\//g, '/'); }, // File upload validation validateFile(file, allowedTypes = [], maxSize = 10 * 1024 * 1024) { const errors = []; // Check file type if (allowedTypes.length > 0) { const fileType = file.type.toLowerCase(); const fileName = file.name.toLowerCase(); const isValidType = allowedTypes.some(type => { if (type.includes('*')) { return fileType.startsWith(type.replace('*', '')); } return fileType === type || fileName.endsWith(type); }); if (!isValidType) { errors.push(`File type not allowed. Allowed types: ${allowedTypes.join(', ')}`); } } // Check file size if (file.size > maxSize) { errors.push(`File too large. Maximum size: ${(maxSize / (1024 * 1024)).toFixed(1)}MB`); } // Check for dangerous file extensions const dangerousExtensions = ['.exe', '.bat', '.cmd', '.scr', '.pif', '.vbs', '.js', '.jar']; const fileName = file.name.toLowerCase(); if (dangerousExtensions.some(ext => fileName.endsWith(ext))) { errors.push('Potentially dangerous file type detected'); } return { isValid: errors.length === 0, errors }; }, // Text input validation validateText(input, options = {}) { const { required = false, minLength = 0, maxLength = Infinity, pattern = null, allowHTML = false } = options; const errors = []; // Check required if (required && (!input || input.trim().length === 0)) { errors.push('This field is required'); } if (input && input.length > 0) { // Check length if (input.length < minLength) { errors.push(`Minimum length is ${minLength} characters`); } if (input.length > maxLength) { errors.push(`Maximum length is ${maxLength} characters`); } // Check pattern if (pattern && !pattern.test(input)) { errors.push('Invalid format'); } // Check for HTML if not allowed if (!allowHTML && /<[^>]*>/.test(input)) { errors.push('HTML tags are not allowed'); } } return { isValid: errors.length === 0, errors, sanitized: allowHTML ? input : this.sanitizeHTML(input) }; }, // Email validation validateEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const isValid = emailRegex.test(email); return { isValid, errors: isValid ? [] : ['Please enter a valid email address'], sanitized: this.sanitizeHTML(email) }; }, // URL validation validateURL(url) { try { const urlObj = new URL(url); // Check for allowed protocols const allowedProtocols = ['http:', 'https:']; if (!allowedProtocols.includes(urlObj.protocol)) { return { isValid: false, errors: ['Only HTTP and HTTPS URLs are allowed'], sanitized: '' }; } return { isValid: true, errors: [], sanitized: urlObj.toString() }; } catch (error) { return { isValid: false, errors: ['Please enter a valid URL'], sanitized: '' }; } } }; ``` ### Advanced HTML Sanitization ```javascript // Advanced HTML Sanitization const HTMLSanitizer = { // Allowed tags and attributes allowedTags: ['p', 'br', 'strong', 'em', 'u', 'ol', 'ul', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'], allowedAttributes: { 'a': ['href', 'title'], 'img': ['src', 'alt', 'width', 'height'], 'all': ['class', 'id'] }, // Comprehensive sanitization sanitize(html) { if (!html || typeof html !== 'string') { return ''; } // Create temporary container const temp = document.createElement('div'); temp.innerHTML = html; // Remove dangerous elements this.removeDangerousElements(temp); // Sanitize allowed elements this.sanitizeAllowedElements(temp); return temp.innerHTML; }, removeDangerousElements(container) { const dangerousTags = [ 'script', 'style', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'textarea', 'select', 'option', 'meta', 'link', 'base' ]; dangerousTags.forEach(tag => { const elements = container.querySelectorAll(tag); elements.forEach(el => el.remove()); }); // Remove comments const walker = document.createTreeWalker( container, NodeFilter.SHOW_COMMENT, null, false ); const comments = []; let node; while (node = walker.nextNode()) { comments.push(node); } comments.forEach(comment => comment.remove()); }, sanitizeAllowedElements(container) { const allElements = container.querySelectorAll('*'); allElements.forEach(el => { const tagName = el.tagName.toLowerCase(); // Remove disallowed tags if (!this.allowedTags.includes(tagName)) { el.remove(); return; } // Clean attributes const allowedAttrs = [ ...(this.allowedAttributes[tagName] || []), ...(this.allowedAttributes.all || []) ]; // Remove dangerous attributes Array.from(el.attributes).forEach(attr => { const attrName = attr.name.toLowerCase(); // Remove event handlers if (attrName.startsWith('on')) { el.removeAttribute(attrName); return; } // Remove javascript: URLs if (attr.value && attr.value.toLowerCase().includes('javascript:')) { el.removeAttribute(attrName); return; } // Remove data attributes (except specific ones) if (attrName.startsWith('data-') && !['data-id', 'data-value'].includes(attrName)) { el.removeAttribute(attrName); return; } // Remove non-allowed attributes if (!allowedAttrs.includes(attrName)) { el.removeAttribute(attrName); } }); }); }, // Safe content setting safeSetHTML(element, content) { if (!element || !content) return; const sanitizedContent = this.sanitize(content); element.innerHTML = sanitizedContent; }, // Safe content appending safeAppendHTML(element, content) { if (!element || !content) return; const sanitizedContent = this.sanitize(content); element.insertAdjacentHTML('beforeend', sanitizedContent); } }; ``` ## XSS Prevention ### Content Security Policy (CSP) ```html ``` ### Django Template Security ```html {% autoescape on %} {{ user_content }} {% endautoescape %} {% load custom_filters %} {{ user_content|safe_html }} Link
Content
``` ### JavaScript XSS Prevention ```javascript // XSS Prevention Utilities const XSSProtection = { // Escape content for HTML context escapeHTML(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; }, // Escape content for JavaScript context escapeJS(str) { return str .replace(/\\/g, '\\\\') .replace(/'/g, "\\'") .replace(/"/g, '\\"') .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') .replace(/\f/g, '\\f') .replace(/\v/g, '\\v') .replace(/\0/g, '\\0'); }, // Escape content for CSS context escapeCSS(str) { return str.replace(/[<>"'&]/g, function(match) { return '\\' + match.charCodeAt(0).toString(16) + ' '; }); }, // Escape content for URL context escapeURL(str) { return encodeURIComponent(str); }, // Safe DOM manipulation safeSetText(element, text) { if (element && typeof text === 'string') { element.textContent = text; } }, safeSetAttribute(element, name, value) { if (element && typeof name === 'string' && typeof value === 'string') { // Prevent dangerous attributes const dangerousAttrs = ['onclick', 'onload', 'onerror', 'onmouseover']; if (dangerousAttrs.includes(name.toLowerCase())) { return false; } element.setAttribute(name, value); return true; } return false; } }; ``` ## CSRF Protection ### Django CSRF Implementation ```python # Django settings for CSRF protection CSRF_COOKIE_SECURE = True # Use HTTPS only CSRF_COOKIE_HTTPONLY = True # Prevent JavaScript access CSRF_COOKIE_SAMESITE = 'Strict' # Prevent cross-site requests CSRF_TRUSTED_ORIGINS = ['https://yourdomain.com'] ``` ### JavaScript CSRF Handling ```javascript // CSRF Token Management const CSRFManager = { // Get CSRF token from cookie getTokenFromCookie() { const cookies = document.cookie.split(';'); for (let cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === 'csrftoken') { return decodeURIComponent(value); } } return null; }, // Get CSRF token from form getTokenFromForm() { const tokenInput = document.querySelector('[name=csrfmiddlewaretoken]'); return tokenInput ? tokenInput.value : null; }, // Get CSRF token from meta tag getTokenFromMeta() { const metaTag = document.querySelector('meta[name="csrf-token"]'); return metaTag ? metaTag.getAttribute('content') : null; }, // Get CSRF token (try multiple sources) getToken() { return this.getTokenFromForm() || this.getTokenFromCookie() || this.getTokenFromMeta(); }, // Add CSRF token to headers addToHeaders(headers = {}) { const token = this.getToken(); if (token) { headers['X-CSRFToken'] = token; } return headers; }, // Add CSRF token to FormData addToFormData(formData) { const token = this.getToken(); if (token) { formData.append('csrfmiddlewaretoken', token); } return formData; } }; // Usage with fetch async function secureRequest(url, options = {}) { const headers = CSRFManager.addToHeaders(options.headers || {}); return fetch(url, { ...options, headers, credentials: 'same-origin' // Include cookies }); } ``` ## File Upload Security ### Client-Side File Validation ```javascript // Secure File Upload Handler const SecureFileUpload = { // Allowed file types allowedTypes: { 'image': ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'], 'document': ['application/pdf', 'text/plain', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], 'data': ['text/csv', 'application/json', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'] }, // Maximum file sizes (in bytes) maxSizes: { 'image': 5 * 1024 * 1024, // 5MB 'document': 10 * 1024 * 1024, // 10MB 'data': 25 * 1024 * 1024 // 25MB }, // Validate file validateFile(file, category = 'document') { const errors = []; // Check file exists if (!file || !file.name) { errors.push('No file selected'); return { isValid: false, errors }; } // Check file size const maxSize = this.maxSizes[category]; if (file.size > maxSize) { errors.push(`File too large. Maximum size: ${(maxSize / (1024 * 1024)).toFixed(1)}MB`); } // Check file type const allowedTypes = this.allowedTypes[category]; if (!allowedTypes.includes(file.type)) { errors.push(`Invalid file type. Allowed types: ${allowedTypes.join(', ')}`); } // Check file name const fileName = file.name.toLowerCase(); const dangerousExtensions = ['.exe', '.bat', '.cmd', '.scr', '.pif', '.vbs', '.js', '.jar', '.php', '.asp', '.aspx']; if (dangerousExtensions.some(ext => fileName.endsWith(ext))) { errors.push('Potentially dangerous file type detected'); } // Check for null bytes if (file.name.includes('\0')) { errors.push('Invalid file name'); } // Check file name length if (file.name.length > 255) { errors.push('File name too long'); } return { isValid: errors.length === 0, errors }; }, // Secure file upload async uploadFile(file, endpoint, category = 'document') { // Validate file const validation = this.validateFile(file, category); if (!validation.isValid) { throw new Error(validation.errors.join(', ')); } // Create FormData const formData = new FormData(); formData.append('file', file); formData.append('category', category); // Add CSRF token CSRFManager.addToFormData(formData); // Upload with progress tracking return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percentComplete = (e.loaded / e.total) * 100; this.updateProgress(percentComplete); } }); xhr.addEventListener('load', () => { if (xhr.status === 200) { try { const response = JSON.parse(xhr.responseText); resolve(response); } catch (error) { reject(new Error('Invalid response format')); } } else { reject(new Error(`Upload failed: ${xhr.status}`)); } }); xhr.addEventListener('error', () => { reject(new Error('Upload failed')); }); xhr.addEventListener('timeout', () => { reject(new Error('Upload timeout')); }); xhr.timeout = 30000; // 30 second timeout xhr.open('POST', endpoint); xhr.send(formData); }); }, updateProgress(percent) { const progressBar = document.querySelector('.upload-progress'); if (progressBar) { progressBar.style.width = `${percent}%`; } } }; ``` ## Content Filtering ### Input Content Filtering ```javascript // Content Filter for User Input const ContentFilter = { // Profanity and inappropriate content filter inappropriateTerms: [ // Add terms as needed (consider using external service) ], // Spam detection patterns spamPatterns: [ /(.)\1{4,}/g, // Repeated characters /http[s]?:\/\/[^\s]+/gi, // URLs /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, // Phone numbers /[A-Z]{3,}/g, // Excessive caps /(.{1,})\1{3,}/g // Repeated words/phrases ], // Filter content filterContent(content) { if (!content || typeof content !== 'string') { return { isValid: true, filtered: content, warnings: [] }; } const warnings = []; let filtered = content; // Check for inappropriate terms const inappropriateFound = this.inappropriateTerms.some(term => content.toLowerCase().includes(term.toLowerCase()) ); if (inappropriateFound) { warnings.push('Content contains inappropriate language'); } // Check for spam patterns let spamScore = 0; this.spamPatterns.forEach(pattern => { const matches = content.match(pattern); if (matches) { spamScore += matches.length; } }); if (spamScore > 3) { warnings.push('Content appears to be spam'); } // Check content length if (content.length > 10000) { warnings.push('Content is very long'); } // Basic content sanitization filtered = content.trim(); return { isValid: warnings.length === 0, filtered, warnings, spamScore }; }, // Rate limiting for content submission submissionTimes: new Map(), checkRateLimit(userId, maxSubmissions = 5, timeWindow = 60000) { const now = Date.now(); const userSubmissions = this.submissionTimes.get(userId) || []; // Remove old submissions const recentSubmissions = userSubmissions.filter(time => now - time < timeWindow ); if (recentSubmissions.length >= maxSubmissions) { return { allowed: false, message: 'Too many submissions. Please wait before submitting again.' }; } // Add current submission recentSubmissions.push(now); this.submissionTimes.set(userId, recentSubmissions); return { allowed: true, remaining: maxSubmissions - recentSubmissions.length }; } }; ``` ## Secure Communication ### API Security ```javascript // Secure API Communication const SecureAPI = { // Base configuration config: { baseURL: '/api/v1', timeout: 30000, retryAttempts: 3, retryDelay: 1000 }, // Make secure request async request(endpoint, options = {}) { const url = `${this.config.baseURL}${endpoint}`; // Default security headers const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', ...options.headers }; // Add CSRF token CSRFManager.addToHeaders(headers); const config = { method: 'GET', headers, credentials: 'same-origin', timeout: this.config.timeout, ...options }; let lastError; // Retry logic for (let attempt = 0; attempt < this.config.retryAttempts; attempt++) { try { const response = await this.makeRequest(url, config); // Check if response is valid if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } // Validate response content type const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { throw new Error('Invalid response content type'); } const data = await response.json(); // Validate response structure if (!this.validateResponse(data)) { throw new Error('Invalid response structure'); } return data; } catch (error) { lastError = error; // Don't retry on client errors if (error.status && error.status >= 400 && error.status < 500) { throw error; } // Wait before retry if (attempt < this.config.retryAttempts - 1) { await this.delay(this.config.retryDelay * (attempt + 1)); } } } throw lastError; }, // Make actual request with timeout async makeRequest(url, config) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), config.timeout); try { const response = await fetch(url, { ...config, signal: controller.signal }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); throw error; } }, // Validate response structure validateResponse(data) { // Basic response validation if (!data || typeof data !== 'object') { return false; } // Check for required fields const requiredFields = ['status', 'data']; return requiredFields.every(field => field in data); }, // Delay utility delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }, // Secure file upload async uploadFile(endpoint, file, additionalData = {}) { const formData = new FormData(); formData.append('file', file); // Add additional data Object.entries(additionalData).forEach(([key, value]) => { formData.append(key, value); }); // Add CSRF token CSRFManager.addToFormData(formData); return this.request(endpoint, { method: 'POST', body: formData, headers: { // Don't set Content-Type for FormData } }); } }; ``` ## Error Handling Security ### Secure Error Display ```javascript // Secure Error Handling const SecureErrorHandler = { // Error types errorTypes: { VALIDATION: 'validation', AUTHENTICATION: 'authentication', AUTHORIZATION: 'authorization', SERVER: 'server', NETWORK: 'network', RATE_LIMIT: 'rate_limit' }, // Handle errors securely handleError(error, context = {}) { console.error('Error occurred:', error, context); // Determine error type const errorType = this.categorizeError(error); // Get user-friendly message const userMessage = this.getUserMessage(errorType, error); // Log error for monitoring (don't expose sensitive info) this.logError(errorType, error, context); // Display error to user this.displayError(userMessage, errorType); // Handle specific error types switch (errorType) { case this.errorTypes.AUTHENTICATION: this.handleAuthError(); break; case this.errorTypes.RATE_LIMIT: this.handleRateLimitError(); break; case this.errorTypes.VALIDATION: this.handleValidationError(error); break; } }, // Categorize error categorizeError(error) { if (error.status === 401) return this.errorTypes.AUTHENTICATION; if (error.status === 403) return this.errorTypes.AUTHORIZATION; if (error.status === 429) return this.errorTypes.RATE_LIMIT; if (error.status >= 400 && error.status < 500) return this.errorTypes.VALIDATION; if (error.status >= 500) return this.errorTypes.SERVER; if (error.name === 'NetworkError') return this.errorTypes.NETWORK; return this.errorTypes.SERVER; }, // Get user-friendly message getUserMessage(errorType, error) { const messages = { [this.errorTypes.VALIDATION]: 'Please check your input and try again.', [this.errorTypes.AUTHENTICATION]: 'Please log in to continue.', [this.errorTypes.AUTHORIZATION]: 'You do not have permission to perform this action.', [this.errorTypes.SERVER]: 'Something went wrong. Please try again later.', [this.errorTypes.NETWORK]: 'Network connection error. Please check your connection.', [this.errorTypes.RATE_LIMIT]: 'Too many requests. Please wait before trying again.' }; return messages[errorType] || 'An unexpected error occurred.'; }, // Log error for monitoring logError(errorType, error, context) { const logData = { type: errorType, message: error.message, status: error.status, timestamp: new Date().toISOString(), context: this.sanitizeContext(context) }; // Send to monitoring service (implement as needed) // this.sendToMonitoring(logData); }, // Sanitize context for logging sanitizeContext(context) { const sanitized = { ...context }; // Remove sensitive information const sensitiveKeys = ['password', 'token', 'key', 'secret']; sensitiveKeys.forEach(key => { if (sanitized[key]) { sanitized[key] = '[REDACTED]'; } }); return sanitized; }, // Display error to user displayError(message, type) { const errorContainer = document.getElementById('errorContainer'); if (!errorContainer) return; const errorElement = document.createElement('div'); errorElement.className = `alert alert-error alert-${type}`; errorElement.innerHTML = ` ⚠️ ${this.escapeHTML(message)} `; errorContainer.appendChild(errorElement); // Auto-remove after 5 seconds setTimeout(() => { if (errorElement.parentNode) { errorElement.remove(); } }, 5000); }, // Handle authentication errors handleAuthError() { // Redirect to login page setTimeout(() => { window.location.href = '/login/'; }, 2000); }, // Handle rate limit errors handleRateLimitError() { // Disable submit buttons temporarily const submitButtons = document.querySelectorAll('[type="submit"]'); submitButtons.forEach(button => { button.disabled = true; setTimeout(() => { button.disabled = false; }, 60000); // 1 minute }); }, // Handle validation errors handleValidationError(error) { if (error.details && typeof error.details === 'object') { Object.entries(error.details).forEach(([field, messages]) => { this.showFieldError(field, messages); }); } }, // Show field-specific error showFieldError(fieldName, messages) { const field = document.getElementById(fieldName); if (!field) return; field.classList.add('is-invalid'); const errorElement = document.getElementById(`${fieldName}-error`); if (errorElement) { errorElement.textContent = Array.isArray(messages) ? messages.join(', ') : messages; } }, // Escape HTML for safe display escapeHTML(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } }; ``` ## Security Testing ### Security Test Suite ```javascript // Security Testing Utilities const SecurityTests = { // Test XSS prevention testXSSPrevention() { const xssPayloads = [ '', 'javascript:alert("XSS")', '', '', '">' ]; xssPayloads.forEach(payload => { const result = HTMLSanitizer.sanitize(payload); console.assert( !result.includes('