UNPKG

@stackmemoryai/stackmemory

Version:

Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.

156 lines (155 loc) 3.96 kB
import { fileURLToPath as __fileURLToPath } from 'url'; import { dirname as __pathDirname } from 'path'; const __filename = __fileURLToPath(import.meta.url); const __dirname = __pathDirname(__filename); import { appendFileSync, existsSync, readFileSync, writeFileSync } from "fs"; import { join } from "path"; import { homedir } from "os"; import { ensureSecureDir } from "./secure-fs.js"; const LOG_DIR = join(homedir(), ".stackmemory", "logs"); const SECURITY_LOG = join(LOG_DIR, "security.log"); const MAX_LOG_ENTRIES = 1e4; let logCount = 0; function logSecurityEvent(type, source, message, details, ip) { try { ensureSecureDir(LOG_DIR); const event = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), type, source, message, ...details && { details }, ...ip && { ip: maskIp(ip) } }; const logLine = JSON.stringify(event) + "\n"; appendFileSync(SECURITY_LOG, logLine, { mode: 384 }); logCount++; if (logCount > MAX_LOG_ENTRIES) { rotateLog(); } } catch { } } function maskIp(ip) { if (!ip) return "unknown"; if (ip === "::1" || ip === "::ffff:127.0.0.1") return "127.0.0.x"; const parts = ip.replace("::ffff:", "").split("."); if (parts.length === 4) { return `${parts[0]}.${parts[1]}.x.x`; } if (ip.includes(":")) { const segments = ip.split(":"); if (segments.length >= 4) { return segments.slice(0, 4).join(":") + ":x:x:x:x"; } } return "masked"; } function rotateLog() { try { if (existsSync(SECURITY_LOG)) { const content = readFileSync(SECURITY_LOG, "utf8"); const lines = content.trim().split("\n"); const keepLines = lines.slice(-MAX_LOG_ENTRIES / 2); writeFileSync(SECURITY_LOG, keepLines.join("\n") + "\n", { mode: 384 }); logCount = keepLines.length; } } catch { } } function logAuthSuccess(source, details) { logSecurityEvent( "auth_success", source, "Authentication successful", details ); } function logAuthFailure(source, reason, ip, details) { logSecurityEvent( "auth_failure", source, `Authentication failed: ${reason}`, details, ip ); } function logRateLimit(source, ip) { logSecurityEvent("rate_limit", source, "Rate limit exceeded", void 0, ip); } function logActionAllowed(source, action) { logSecurityEvent( "action_allowed", source, `Action executed: ${action.substring(0, 100)}` ); } function logActionBlocked(source, action, reason) { logSecurityEvent("action_blocked", source, `Action blocked: ${reason}`, { action: action.substring(0, 100) }); } function logConfigInvalid(source, errors) { logSecurityEvent("config_invalid", source, "Invalid config rejected", { errors: errors.slice(0, 5) }); } function logWebhookRequest(source, method, path, ip) { logSecurityEvent( "webhook_request", source, `${method} ${path}`, void 0, ip ); } function logSignatureInvalid(source, ip) { logSecurityEvent( "signature_invalid", source, "Invalid request signature", void 0, ip ); } function logBodyTooLarge(source, size, ip) { logSecurityEvent( "body_too_large", source, `Request body too large: ${size} bytes`, void 0, ip ); } function logContentTypeInvalid(source, contentType, ip) { logSecurityEvent( "content_type_invalid", source, `Invalid content type: ${contentType}`, void 0, ip ); } function logCleanup(source, expiredPrompts, oldActions) { if (expiredPrompts > 0 || oldActions > 0) { logSecurityEvent("cleanup", source, "Cleanup completed", { expiredPrompts, oldActions }); } } export { logActionAllowed, logActionBlocked, logAuthFailure, logAuthSuccess, logBodyTooLarge, logCleanup, logConfigInvalid, logContentTypeInvalid, logRateLimit, logSecurityEvent, logSignatureInvalid, logWebhookRequest }; //# sourceMappingURL=security-logger.js.map