Spaces:
Running
Running
feat: deploy sustainability, staff command center, security hardening, accessibility and UX polish to HF
ca26788 | /** | |
| * Security & Input Safety Service | |
| */ | |
| export class InputSafety { | |
| validateFrame(blob) { return InputSafety.validateFrame(blob); } | |
| validateGPS(arg1, arg2) { return InputSafety.validateGPS(arg1, arg2); } | |
| sanitizeText(text) { return InputSafety.sanitizeText(text); } | |
| scrubPromptInjection(text) { return InputSafety.scrubPromptInjection(text); } | |
| clampOutput(text, maxLength) { return InputSafety.clampOutput(text, maxLength); } | |
| sanitizeForDOM(str) { return InputSafety.sanitizeForDOM(str); } | |
| validateQuizPayload(payload) { return InputSafety.validateQuizPayload(payload); } | |
| static validateFrame(blob) { | |
| if (!blob) return { valid: false, reason: 'No frame provided', error: 'No frame provided' }; | |
| const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; | |
| if (blob.type && !allowedTypes.includes(blob.type.toLowerCase())) { | |
| return { valid: false, reason: `Unsupported file type: ${blob.type}`, error: `Invalid image type: ${blob.type}` }; | |
| } | |
| const maxBytes = 4 * 1024 * 1024; // 4MB | |
| if (blob.size && blob.size > maxBytes) { | |
| return { valid: false, reason: 'Frame size exceeds 4MB cap', error: 'Frame exceeds 4MB cap' }; | |
| } | |
| return { valid: true }; | |
| } | |
| static validateGPS(arg1, arg2) { | |
| let lat, lng; | |
| if (typeof arg1 === 'object' && arg1 !== null) { | |
| lat = arg1.lat; | |
| lng = arg1.lng; | |
| } else { | |
| lat = arg1; | |
| lng = arg2; | |
| } | |
| if (lat === undefined || lng === undefined) { | |
| return { valid: false, reason: 'Missing lat/lng' }; | |
| } | |
| const latNum = Number(lat); | |
| const lngNum = Number(lng); | |
| if (isNaN(latNum) || latNum < -90 || latNum > 90) { | |
| return { valid: false, reason: 'Latitude out of range [-90, 90]' }; | |
| } | |
| if (isNaN(lngNum) || lngNum < -180 || lngNum > 180) { | |
| return { valid: false, reason: 'Longitude out of range [-180, 180]' }; | |
| } | |
| return { valid: true }; | |
| } | |
| static sanitizeText(text) { | |
| if (!text || typeof text !== 'string') return ''; | |
| return text | |
| .replace(/<\/?[^>]+(>|$)/g, '') | |
| .replace(/on\w+=\s*["'][^"']*["']/gi, '') | |
| .trim(); | |
| } | |
| static scrubPromptInjection(text) { | |
| if (!text || typeof text !== 'string') return ''; | |
| const injectionPatterns = [ | |
| /ignore\s+(all\s+)?previous\s+instructions/gi, | |
| /\[?system\]?\s*:\s*/gi, | |
| /you\s+are\s+now\s+(a|DAN)?/gi, | |
| /override\s+prompt/gi | |
| ]; | |
| let cleaned = text; | |
| injectionPatterns.forEach(pattern => { | |
| cleaned = cleaned.replace(pattern, ''); | |
| }); | |
| return cleaned; | |
| } | |
| static clampOutput(text, maxLength = 2000) { | |
| if (!text || typeof text !== 'string') return ''; | |
| if (text.length <= maxLength) return text; | |
| return text.substring(0, maxLength); | |
| } | |
| static sanitizeForDOM(str) { | |
| if (!str) return ''; | |
| return String(str) | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/"/g, '"') | |
| .replace(/'/g, '''); | |
| } | |
| static validateQuizPayload(payload) { | |
| if (!payload || typeof payload !== 'object') return { valid: false, reason: 'Payload must be an object' }; | |
| // Format A: { questionId, answer, timestamp } | |
| if (payload.questionId !== undefined || payload.answer !== undefined) { | |
| if (!payload.questionId) return { valid: false, reason: 'Missing required field: questionId' }; | |
| if (payload.answer === undefined || payload.answer === null || payload.answer === '' || (typeof payload.answer === 'string' && !payload.answer.trim())) { | |
| return { valid: false, reason: 'Missing or empty required field: answer' }; | |
| } | |
| if (!payload.timestamp) return { valid: false, reason: 'Missing required field: timestamp' }; | |
| return { valid: true }; | |
| } | |
| // Format B: { question, options } | |
| if (!payload.question || typeof payload.question !== 'string') return { valid: false, reason: 'Missing question' }; | |
| if (!Array.isArray(payload.options) || payload.options.length < 2) return { valid: false, reason: 'Requires at least 2 options' }; | |
| return { valid: true }; | |
| } | |
| } | |