@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
JavaScript
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