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); | |
} | |
})(); | |