All files / shared/src/utils security.ts

63.26% Statements 31/49
100% Branches 7/7
75% Functions 3/4
63.26% Lines 31/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86              1x 90x 90x 90x 90x 90x 90x 90x 90x 90x 90x   90x 90x         1x 5x 5x 5x 5x 5x 5x 1x 1x   3x 5x   1x 1x 5x             1x 83x 83x         1x                                                              
/**
 * Security utilities for XSS prevention and input sanitization
 */
 
/**
 * Sanitizes a string to prevent XSS attacks by escaping HTML entities
 */
export function sanitizeHtml(input: string): string {
  const entityMap: Record<string, string> = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;',
    '/': '&#x2F;',
    '`': '&#x60;',
    '=': '&#x3D;',
  };
 
  return input.replace(/[&<>"'`=/]/g, (s) => entityMap[s]);
}
 
/**
 * Safely stringify JSON data for display, preventing XSS
 */
export function safeJsonStringify(
  value: unknown,
  space?: string | number,
): string {
  try {
    const jsonString = JSON.stringify(value, null, space);
    if (jsonString === undefined) {
      return 'undefined';
    }
    // Sanitize the JSON string to prevent XSS
    return sanitizeHtml(jsonString);
  } catch (_error) {
    // If JSON.stringify fails, return a safe error message
    return '[Invalid JSON data]';
  }
}
 
/**
 * Sanitizes user input text by escaping HTML entities.
 * This is the most reliable XSS prevention - by escaping all special characters,
 * no HTML tags or attributes can be injected regardless of the input.
 */
export function sanitizeUserInput(input: string): string {
  return sanitizeHtml(input).trim();
}
 
/**
 * Sanitizes agent metadata object keys and values
 */
export function sanitizeMetadata(
  metadata: Record<string, unknown>,
): Record<string, string> {
  const sanitized: Record<string, string> = {};
 
  for (const [key, value] of Object.entries(metadata)) {
    // Sanitize the key
    const sanitizedKey = sanitizeUserInput(key);
 
    // Remove HTML entities to check if anything meaningful remains
    const keyWithoutEntities = sanitizedKey.replace(/&[a-zA-Z0-9#]+;/g, '');
 
    // Skip if key becomes empty after sanitization or only contains HTML entities
    if (!keyWithoutEntities.trim()) continue;
 
    // Sanitize the value
    let sanitizedValue: string;
    if (typeof value === 'object' && value !== null) {
      sanitizedValue = safeJsonStringify(value, 2);
    } else {
      sanitizedValue = sanitizeUserInput(String(value));
    }
 
    // Skip if value becomes empty after sanitization
    if (sanitizedValue.trim().length === 0) continue;
 
    sanitized[sanitizedKey] = sanitizedValue;
  }
 
  return sanitized;
}