@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
245 lines • 8.63 kB
JavaScript
/**
* SVG File Processor
*
* Processes SVG files as TEXT content, not as images, because:
* 1. Most AI vision models don't support SVG format directly
* 2. SVG is XML-based and can be analyzed as code/markup
* 3. SVG can contain security risks (scripts, XSS vectors)
*
* Security: Uses OWASP-compliant allowlist-based SVG sanitization
*
* @module processors/markup/SvgProcessor
*
* @example
* ```typescript
* import { svgProcessor, processSvg, isSvgFile } from "./markup/SvgProcessor.js";
*
* // Check if file is SVG
* if (isSvgFile(mimetype, filename)) {
* const result = await processSvg(fileInfo);
* if (result.success) {
* console.log('Sanitized SVG:', result.data.textContent);
* if (result.data.securityWarnings.length > 0) {
* console.warn('Security warnings:', result.data.securityWarnings);
* }
* }
* }
* ```
*/
import { isSvgContentSafe, sanitizeSvgContent, } from "../../utils/sanitizers/svg.js";
import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
// =============================================================================
// CONSTANTS
// =============================================================================
/** SVG MIME type */
const SUPPORTED_SVG_TYPES = ["image/svg+xml"];
/** SVG file extension */
const SUPPORTED_SVG_EXTENSIONS = [".svg"];
// =============================================================================
// TYPES
// =============================================================================
// Re-import for local use within this file
// =============================================================================
// SVG PROCESSOR
// =============================================================================
/**
* SVG Processor - processes SVG as TEXT, not as image.
*
* Why text instead of image:
* 1. Most AI vision models don't support SVG format
* 2. SVG is XML-based and can be analyzed as markup
* 3. Security: SVG can contain scripts and XSS vectors
*
* Priority: 5 (before IMAGE at priority 10)
*
* @example
* ```typescript
* const processor = new SvgProcessor();
*
* const result = await processor.processFile({
* id: 'svg-123',
* name: 'diagram.svg',
* mimetype: 'image/svg+xml',
* size: 2048,
* url: 'https://example.com/diagram.svg',
* });
*
* if (result.success) {
* // Use sanitized SVG text content
* console.log(result.data.textContent);
* }
* ```
*/
export class SvgProcessor extends BaseFileProcessor {
constructor() {
super({
maxSizeMB: 5,
timeoutMs: 30000,
supportedMimeTypes: [...SUPPORTED_SVG_TYPES],
supportedExtensions: [...SUPPORTED_SVG_EXTENSIONS],
fileTypeName: "SVG",
defaultFilename: "image.svg",
});
}
/**
* Validate downloaded SVG file.
* Checks for valid XML structure (SVG must contain <svg> element).
*
* @param buffer - Downloaded file content
* @param fileInfo - Original file information
* @returns null if valid, error message if invalid
*/
async validateDownloadedFile(buffer, _fileInfo) {
const content = buffer.toString("utf-8").trim();
// Check for valid SVG content
// Valid SVG can start with: <?xml, <!DOCTYPE, <svg, or just contain <svg
const startsWithXml = content.startsWith("<?xml");
const startsWithDoctype = content.toLowerCase().startsWith("<!doctype");
const startsWithSvg = content.toLowerCase().startsWith("<svg");
const containsSvgTag = content.toLowerCase().includes("<svg");
if (!startsWithXml &&
!startsWithDoctype &&
!startsWithSvg &&
!containsSvgTag) {
// Check if it might be HTML (download error page)
if (content.toLowerCase().includes("<html")) {
return "Download failed - received HTML instead of SVG content";
}
return "Invalid SVG - missing <svg> element";
}
return null;
}
/**
* Build processed SVG result with sanitized content.
* Applies security sanitization to remove potentially malicious content.
*
* @param buffer - Downloaded file content
* @param fileInfo - Original file information
* @returns Processed SVG result with sanitized text content
*/
buildProcessedResult(buffer, fileInfo) {
const rawContent = buffer.toString("utf-8");
const filename = this.getFilename(fileInfo);
// Check if content is safe before sanitization
const wasSafe = isSvgContentSafe(rawContent);
// Build security warnings (initialized before try/catch so catch block can append)
const securityWarnings = [];
// Apply security sanitization using allowlist-based approach
let textContent;
try {
textContent = sanitizeSvgContent(rawContent);
}
catch {
// Fail closed: if sanitization fails (e.g., malformed XML with XXE),
// return a safe empty SVG instead of attempting regex-based cleanup.
// Regex cannot safely sanitize XML/HTML (context-free grammar).
textContent = '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
securityWarnings.push("SVG sanitization failed - malformed content replaced with empty SVG for security");
}
if (!wasSafe) {
securityWarnings.push("SVG contained potentially unsafe content that was sanitized");
}
// Check if content was actually modified
const contentWasModified = textContent !== rawContent;
return {
textContent,
// Only include raw content if sanitization actually changed it (for debugging)
rawContent: contentWasModified ? rawContent : undefined,
sanitized: !wasSafe || contentWasModified,
securityWarnings,
buffer,
mimetype: fileInfo.mimetype || "image/svg+xml",
size: fileInfo.size,
filename,
};
}
}
// =============================================================================
// SINGLETON INSTANCE
// =============================================================================
/**
* Singleton SVG processor instance.
* Use this for most processing needs.
*
* @example
* ```typescript
* import { svgProcessor } from "./markup/SvgProcessor.js";
*
* const result = await svgProcessor.processFile(fileInfo);
* ```
*/
export const svgProcessor = new SvgProcessor();
// =============================================================================
// HELPER FUNCTIONS
// =============================================================================
/**
* Check if a file is an SVG file.
*
* @param mimetype - MIME type of the file
* @param filename - Filename (for extension-based detection)
* @returns true if the file is an SVG
*
* @example
* ```typescript
* if (isSvgFile('image/svg+xml', 'diagram.svg')) {
* // Handle as SVG
* }
*
* // Also works with just filename
* if (isSvgFile('', 'icon.svg')) {
* // Handle as SVG based on extension
* }
* ```
*/
export function isSvgFile(mimetype, filename) {
return svgProcessor.isFileSupported(mimetype, filename);
}
/**
* Validate SVG file size against configured limit.
*
* @param sizeBytes - File size in bytes
* @returns true if size is within the allowed limit (5 MB)
*
* @example
* ```typescript
* if (!validateSvgSize(fileInfo.size)) {
* console.error('SVG file is too large');
* }
* ```
*/
export function validateSvgSize(sizeBytes) {
const maxBytes = 5 * 1024 * 1024; // 5 MB
return sizeBytes <= maxBytes;
}
/**
* Process a single SVG file.
* Convenience function that uses the singleton processor.
*
* @param fileInfo - File information (can include URL or buffer)
* @param options - Optional processing options (auth headers, timeout, retry config)
* @returns Processing result with sanitized SVG text content
*
* @example
* ```typescript
* const result = await processSvg({
* id: 'svg-123',
* name: 'diagram.svg',
* mimetype: 'image/svg+xml',
* size: 2048,
* buffer: svgBuffer,
* });
*
* if (result.success) {
* console.log('Processed SVG:', result.data.textContent);
* if (result.data.sanitized) {
* console.log('Content was sanitized for security');
* }
* } else {
* console.error('Processing failed:', result.error.userMessage);
* }
* ```
*/
export async function processSvg(fileInfo, options) {
return svgProcessor.processFile(fileInfo, options);
}
//# sourceMappingURL=SvgProcessor.js.map