Virtual-Kimi / kimi-js /kimi-security.js
VirtualKimi's picture
Upload 38 files
798bcc6 verified
// ===== 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);
}
})();