/**
* XSS PREVENTION - Template Escape Methods
* Always escape output in templates to prevent Cross-Site Scripting attacks.
*/
export interface EscapeMethod {
name: string;
use: string;
example: string;
deprecated?: boolean;
notes?: string;
}
export const XSS_ESCAPE_METHODS: Record<string, EscapeMethod> = {
'escapeHtml': {
name: 'escapeHtml',
use: 'General HTML content - text that will be displayed as HTML content',
example: '<?= $escaper->escapeHtml($block->getData("name")) ?>',
notes: 'Most commonly used. Converts special characters to HTML entities.'
},
'escapeHtmlAttr': {
name: 'escapeHtmlAttr',
use: 'HTML attribute values - data placed inside HTML attributes',
example: '<div data-value="<?= $escaper->escapeHtmlAttr($value) ?>" class="<?= $escaper->escapeHtmlAttr($class) ?>">',
notes: 'Use for any dynamic value inside an HTML attribute.'
},
'escapeUrl': {
name: 'escapeUrl',
use: 'URLs in href/src attributes - links and resource URLs',
example: '<a href="<?= $escaper->escapeUrl($url) ?>">Link</a>\n<img src="<?= $escaper->escapeUrl($imageUrl) ?>">',
notes: 'Encodes URL special characters. Use for trusted URLs only.'
},
'escapeJs': {
name: 'escapeJs',
use: 'JavaScript string values - data embedded in JavaScript code',
example: '<script>var userName = "<?= $escaper->escapeJs($name) ?>";</script>',
notes: 'Escapes quotes and special characters for safe JavaScript string embedding.'
},
'escapeCss': {
name: 'escapeCss',
use: 'CSS values - dynamic values in style attributes or style tags',
example: '<style>.item { color: <?= $escaper->escapeCss($color) ?>; }</style>\n<div style="background: <?= $escaper->escapeCss($bg) ?>">',
notes: 'Escapes CSS special characters to prevent CSS injection.'
},
'escapeJsQuote': {
name: 'escapeJsQuote',
use: 'JavaScript quoted strings (DEPRECATED - prefer escapeJs)',
example: '<?= $escaper->escapeJsQuote($value) ?>',
deprecated: true,
notes: 'Deprecated. Use escapeJs() instead for better security.'
},
'escapeQuote': {
name: 'escapeQuote',
use: 'Quoted strings (DEPRECATED - prefer escapeHtmlAttr)',
example: '<?= $escaper->escapeQuote($value) ?>',
deprecated: true,
notes: 'Deprecated. Use escapeHtmlAttr() instead.'
},
'escapeXssInUrl': {
name: 'escapeXssInUrl',
use: 'URLs that may contain XSS payloads - untrusted/user-provided URLs',
example: '<?= $escaper->escapeXssInUrl($untrustedUrl) ?>',
notes: 'Use when URL source is not fully trusted. Removes javascript: and other dangerous protocols.'
},
};
export interface NoEscapeRule {
annotation: string;
validUses: string[];
invalidUses: string[];
example: string;
}
export const NO_ESCAPE_ANNOTATION: NoEscapeRule = {
annotation: '/* @noEscape */',
validUses: [
'Output from getJsLayout() - already JSON encoded and safe',
'Output from another escape method - already escaped',
'Static HTML that contains no user data',
'Pre-escaped CMS content from trusted sources',
'JSON data that was encoded with json_encode()',
],
invalidUses: [
'Any user input - NEVER skip escaping user data',
'Database values without prior escaping',
'URL parameters or query strings',
'Form data or POST values',
'Data from external APIs without validation',
'File contents or names',
],
example: '<?= /* @noEscape */ $block->getJsLayout() ?>'
};
/**
* Allowed functions that don't need escaping (they return safe values)
*/
export const ALLOWED_FUNCTIONS_NO_ESCAPE = [
'count', // Returns integer
'strlen', // Returns integer
'sizeof', // Returns integer
];
/**
* Methods that contain "html" in the name are considered escaped
*/
export const HTML_METHOD_PATTERN = 'html';
/**
* Get the appropriate escape method for a context
*/
export function getEscapeMethodForContext(context: string): EscapeMethod | null {
const contextMap: Record<string, string> = {
'html': 'escapeHtml',
'text': 'escapeHtml',
'content': 'escapeHtml',
'attribute': 'escapeHtmlAttr',
'attr': 'escapeHtmlAttr',
'url': 'escapeUrl',
'href': 'escapeUrl',
'src': 'escapeUrl',
'link': 'escapeUrl',
'javascript': 'escapeJs',
'js': 'escapeJs',
'script': 'escapeJs',
'css': 'escapeCss',
'style': 'escapeCss',
};
const methodName = contextMap[context.toLowerCase()];
return methodName ? XSS_ESCAPE_METHODS[methodName] : null;
}
/**
* Template escaping best practices
*/
export const TEMPLATE_ESCAPING_BEST_PRACTICES = [
'Always use $escaper (injected via constructor) instead of $block->escape*()',
'Escape ALL dynamic output - when in doubt, escape it',
'Use escapeHtml() for general content by default',
'Use escapeHtmlAttr() for ALL attribute values',
'Use escapeUrl() for href and src attributes',
'Use escapeJs() when embedding PHP values in JavaScript',
'Never use @noEscape for user-provided data',
'Review all @noEscape annotations during code review',
'Prefer ViewModels over direct block methods for data',
];