Spaces:
Running
Running
| // ===== KIMI SECURITY & VALIDATION CONFIGURATION ===== | |
| window.KIMI_SECURITY_CONFIG = { | |
| // Input validation limits | |
| MAX_MESSAGE_LENGTH: 5000, | |
| MAX_API_KEY_LENGTH: 200, | |
| MIN_API_KEY_LENGTH: 10, | |
| // Security settings | |
| API_KEY_PATTERNS: [ | |
| /^sk-or-v1-[a-zA-Z0-9]{16,}$/, // OpenRouter pattern (relaxed length) | |
| /^sk-[a-zA-Z0-9_\-]{16,}$/, // OpenAI and similar (relaxed) | |
| /^[a-zA-Z0-9_\-]{16,}$/ // Generic API key fallback | |
| ], | |
| // Cache settings | |
| CACHE_MAX_AGE: 300000, // 5 minutes | |
| CACHE_MAX_SIZE: 100, | |
| // Performance settings | |
| DEBOUNCE_DELAY: 300, | |
| BATCH_DELAY: 800, | |
| THROTTLE_LIMIT: 1000, | |
| // Error messages | |
| ERRORS: { | |
| INVALID_INPUT: "Invalid input provided", | |
| MESSAGE_TOO_LONG: "Message too long. Please keep it under {max} characters.", | |
| INVALID_API_KEY: "Invalid API key format", | |
| NETWORK_ERROR: "Network error. Please check your connection.", | |
| SYSTEM_ERROR: "System error occurred. Please try again." | |
| } | |
| }; | |
| // Validation utilities using the configuration | |
| window.KIMI_VALIDATORS = { | |
| validateMessage: message => { | |
| if (!message || typeof message !== "string") return { valid: false, error: "INVALID_INPUT" }; | |
| if (message.length > window.KIMI_SECURITY_CONFIG.MAX_MESSAGE_LENGTH) { | |
| return { | |
| valid: false, | |
| error: "MESSAGE_TOO_LONG", | |
| params: { max: window.KIMI_SECURITY_CONFIG.MAX_MESSAGE_LENGTH } | |
| }; | |
| } | |
| return { valid: true }; | |
| }, | |
| validateApiKey: key => { | |
| if (!key || typeof key !== "string") return false; | |
| if (key.length < window.KIMI_SECURITY_CONFIG.MIN_API_KEY_LENGTH) return false; | |
| if (key.length > window.KIMI_SECURITY_CONFIG.MAX_API_KEY_LENGTH) return false; | |
| return window.KIMI_SECURITY_CONFIG.API_KEY_PATTERNS.some(pattern => pattern.test(key)); | |
| }, | |
| validateSliderValue: (value, type) => { | |
| // Use centralized config from KIMI_CONFIG | |
| if (window.KIMI_CONFIG && window.KIMI_CONFIG.validate) { | |
| return window.KIMI_CONFIG.validate(value, type); | |
| } | |
| // Fallback if config not available | |
| const num = parseFloat(value); | |
| if (isNaN(num)) return { valid: false, value: 0 }; | |
| return { valid: true, value: num }; | |
| } | |
| }; | |
| window.KIMI_SECURITY_INITIALIZED = true; | |
| // ===== Global Input Hardening (anti-autofill and password manager suppression) ===== | |
| (function setupGlobalInputHardening() { | |
| try { | |
| const ATTRS = { | |
| autocomplete: "off", | |
| autocapitalize: "none", | |
| autocorrect: "off", | |
| spellcheck: "false", | |
| inputmode: "text", | |
| "aria-autocomplete": "none", | |
| "data-lpignore": "true", | |
| "data-1p-ignore": "true", | |
| "data-bwignore": "true", | |
| "data-form-type": "other" | |
| }; | |
| const API_INPUT_ID = "openrouter-api-key"; | |
| function hardenElement(el) { | |
| if (!el || !(el instanceof HTMLElement)) return; | |
| const tag = el.tagName; | |
| if (tag !== "INPUT" && tag !== "TEXTAREA") return; | |
| // Do not convert other inputs to password; only enforce anti-autofill attributes | |
| for (const [k, v] of Object.entries(ATTRS)) { | |
| try { | |
| if (el.getAttribute(k) !== v) el.setAttribute(k, v); | |
| } catch {} | |
| } | |
| // Special handling for the API key field: ensure it's treated as non-credential by managers | |
| if (el.id === API_INPUT_ID) { | |
| try { | |
| // Keep password type by default for masking; JS toggler can switch to text on demand | |
| if (!el.hasAttribute("type")) el.setAttribute("type", "password"); | |
| // Explicitly set a non-credential-ish name/value context | |
| if (el.getAttribute("name") !== "openrouter_api_key") el.setAttribute("name", "openrouter_api_key"); | |
| if (el.getAttribute("autocomplete") !== "new-password") el.setAttribute("autocomplete", "new-password"); | |
| } catch {} | |
| } else { | |
| // For non-API inputs, if browser set type=password by heuristics, revert to text | |
| try { | |
| if (el.getAttribute("type") === "password" && el.id !== API_INPUT_ID) { | |
| el.setAttribute("type", "text"); | |
| } | |
| } catch {} | |
| } | |
| } | |
| function hardenAll(scope = document) { | |
| const nodes = scope.querySelectorAll("input, textarea"); | |
| nodes.forEach(hardenElement); | |
| } | |
| // Initial pass | |
| if (document.readyState === "loading") { | |
| document.addEventListener("DOMContentLoaded", () => hardenAll()); | |
| } else { | |
| hardenAll(); | |
| } | |
| // Observe dynamic DOM changes | |
| const mo = new MutationObserver(mutations => { | |
| for (const m of mutations) { | |
| if (m.type === "childList") { | |
| m.addedNodes.forEach(node => { | |
| if (node.nodeType === 1) { | |
| if (node.matches && (node.matches("input") || node.matches("textarea"))) { | |
| hardenElement(node); | |
| } | |
| const descendants = node.querySelectorAll ? node.querySelectorAll("input, textarea") : []; | |
| descendants.forEach(hardenElement); | |
| } | |
| }); | |
| } | |
| } | |
| }); | |
| try { | |
| mo.observe(document.documentElement || document.body, { | |
| subtree: true, | |
| childList: true | |
| }); | |
| } catch {} | |
| // Expose for debugging if needed | |
| window._kimiInputHardener = { hardenAll }; | |
| } catch (e) { | |
| // Fail-safe: never block the app | |
| console.warn("Input hardening setup error:", e); | |
| } | |
| })(); | |