@ordojs/security
Version:
Security package for OrdoJS with XSS, CSRF, and injection protection
130 lines (112 loc) • 3.49 kB
text/typescript
import createDOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window as any);
/**
* Configuration options for HTML sanitization
*/
export interface SanitizerOptions {
/**
* List of allowed HTML tags
* @default ['a', 'b', 'br', 'code', 'div', 'em', 'i', 'li', 'ol', 'p', 'pre', 'span', 'strong', 'ul']
*/
allowedTags?: string[];
/**
* List of allowed HTML attributes
* @default ['href', 'target', 'class', 'id', 'style']
*/
allowedAttributes?: string[] | Record<string, string[]>;
/**
* Whether to allow data attributes (data-*)
* @default false
*/
allowDataAttributes?: boolean;
/**
* Whether to strip all HTML and return text only
* @default false
*/
stripAllTags?: boolean;
}
/**
* Default sanitizer options with safe defaults
*/
const DEFAULT_OPTIONS: SanitizerOptions = {
allowedTags: ['a', 'b', 'br', 'code', 'div', 'em', 'i', 'li', 'ol', 'p', 'pre', 'span', 'strong', 'ul'],
allowedAttributes: ['href', 'target', 'class', 'id', 'style'],
allowDataAttributes: false,
stripAllTags: false,
};
/**
* HTML Sanitizer class for preventing XSS attacks
* Uses DOMPurify under the hood with configurable options
*/
export class HtmlSanitizer {
private options: SanitizerOptions;
/**
* Create a new HtmlSanitizer instance
* @param options Sanitizer configuration options
*/
constructor(options: SanitizerOptions = {}) {
this.options = { ...DEFAULT_OPTIONS, ...options };
}
/**
* Sanitize HTML content to prevent XSS attacks
* @param html HTML content to sanitize
* @returns Sanitized HTML
*/
sanitize(html: string): string {
if (this.options.stripAllTags) {
return DOMPurify.sanitize(html, { ALLOWED_TAGS: [] }) as unknown as string;
}
const config: any = {
ALLOWED_TAGS: this.options.allowedTags,
ALLOW_DATA_ATTR: this.options.allowDataAttributes,
};
// Handle allowedAttributes which can be string[] or Record<string, string[]>
if (Array.isArray(this.options.allowedAttributes)) {
config.ALLOWED_ATTR = this.options.allowedAttributes;
} else if (this.options.allowedAttributes) {
config.ALLOWED_ATTR = this.options.allowedAttributes;
}
return DOMPurify.sanitize(html, config) as unknown as string;
}
/**
* Update sanitizer options
* @param options New sanitizer options
*/
updateOptions(options: Partial<SanitizerOptions>): void {
this.options = { ...this.options, ...options };
}
/**
* Create a sanitizer with strict settings (minimal allowed tags)
* @returns A new HtmlSanitizer instance with strict settings
*/
static createStrict(): HtmlSanitizer {
return new HtmlSanitizer({
allowedTags: ['b', 'em', 'i', 'strong', 'span'],
allowedAttributes: ['class'],
allowDataAttributes: false,
});
}
/**
* Create a sanitizer that strips all HTML tags
* @returns A new HtmlSanitizer instance that strips all HTML
*/
static createTextOnly(): HtmlSanitizer {
return new HtmlSanitizer({
stripAllTags: true,
});
}
}
/**
* Create a default HTML sanitizer instance
*/
export const defaultSanitizer = new HtmlSanitizer();
/**
* Sanitize HTML content using the default sanitizer
* @param html HTML content to sanitize
* @returns Sanitized HTML
*/
export function sanitizeHtml(html: string): string {
return defaultSanitizer.sanitize(html);
}