Skip to content

Security

Chris & Mike edited this page Mar 11, 2026 · 16 revisions

Security

Security features and best practices for Memory Journal MCP Server.

Local-First Design: Your project context stays on your machine. No external API calls, no cloud dependencies - your development history remains private.


Security Model

Memory Journal is local-first:

  • All data stored locally in SQLite
  • No external API calls
  • No telemetry or analytics
  • You own and control your data

Input Validation

Content Size Limits

const MAX_CONTENT_SIZE = 50 * 1024; // 50KB per entry
const MAX_TAG_LENGTH = 100; // 100 characters per tag

Why limits:

  • Prevent resource exhaustion
  • Ensure reasonable performance
  • Protect database integrity

SQL Injection Prevention

All queries use parameterized statements:

// Safe - parameterized query
db.run("INSERT INTO memory_journal (content) VALUES (?)", [userContent]);

// NEVER do this (vulnerable to SQL injection)
db.run(`INSERT INTO memory_journal (content) VALUES ('${userContent}')`); // DON'T!

Benefits:

  • SQL injection impossible
  • Safe handling of quotes and special characters
  • Database-level validation

LIKE Pattern Sanitization

Search queries using SQL LIKE clauses have wildcards (%, _, \) escaped to prevent unintended pattern matching:

// User searches for "100%" — safely escaped to "100\%"
sanitizeSearchQuery("100%"); // Returns '100\\%'

Path Traversal Protection

Backup and restore filenames are validated against path traversal attacks:

// Rejects malicious filenames like "../../etc/passwd"
assertNoPathTraversal(filename); // Throws PathTraversalError

🌐 HTTP Transport Security

When running in HTTP mode (--transport http), the following security measures apply:

Security Headers

Every HTTP response includes these security headers:

  • X-Content-Type-Options: nosniff
  • X-Frame-Options: DENY
  • Content-Security-Policy: default-src 'none'; frame-ancestors 'none'
  • Cache-Control: no-store, no-cache, must-revalidate
  • Referrer-Policy: no-referrer
  • Permissions-Policy: camera=(), microphone=(), geolocation=()

Rate Limiting

HTTP endpoints are rate-limited to 100 requests per minute per IP to prevent abuse:

// Automatic — no configuration needed
// Returns 429 Too Many Requests when exceeded

CORS Configuration

# Restrict CORS to specific origins (recommended)
memory-journal-mcp --transport http --cors-origin "http://localhost:3000"

# Multiple origins (comma-separated)
memory-journal-mcp --transport http --cors-origin "http://localhost:3000,https://app.example.com"

HSTS

Opt-in via --enable-hsts flag. When enabled, sends Strict-Transport-Security: max-age=31536000; includeSubDomains on all responses.

Body Size Limit

Request bodies are limited to 1MB to prevent memory exhaustion.

Session Management

  • UUID-based session IDs via crypto.randomUUID()
  • 30-minute timeout with automatic cleanup
  • 5-minute sweep interval for expired sessions

Docker Security

Non-Root Execution

# Create non-root user
RUN addgroup -g 1000 appuser && \
    adduser -D -u 1000 -G appuser appuser

# Switch to non-root user
USER appuser

Benefits:

  • Container cannot modify host system
  • Reduced attack surface
  • Follows least privilege principle

Minimal Base Image

FROM node:24-alpine

Alpine Linux benefits:

  • Small attack surface (~5MB base)
  • Security-focused distribution
  • Fewer vulnerabilities
  • Fast updates

No Privileged Access

{
  "command": "docker",
  "args": [
    "run",
    "--rm",
    "-i",
    // NO --privileged flag
    // NO --cap-add flags
    "-v",
    "./data:/app/data",
    "writenotenow/memory-journal-mcp:latest"
  ]
}

Volume Mount Security

{
  "args": [
    "-v",
    "./data:/app/data:rw" // Only data directory
    // NOT: "-v", "/:/host" - would expose entire host
  ]
}

Best practices:

  • Mount only necessary directories
  • Use read-only mounts where possible
  • Avoid mounting sensitive host directories

Supply Chain Security

SHA-Pinned Images

Recommended (highest security):

# Use SHA-256 digest for immutable reference
docker pull writenotenow/memory-journal-mcp@sha256:abc123...

# Find SHA digests at:
# https://hub.docker.com/r/writenotenow/memory-journal-mcp/tags

Benefits:

  • Immutable image reference
  • Protection against tag hijacking
  • Reproducible deployments
  • Supply chain verification

Tag-based (convenience):

docker pull writenotenow/memory-journal-mcp:latest

Database Security

sql.js In-Memory Model

sql.js operates as an in-memory WASM database. The database is loaded from a file on startup and saved back on changes.

-- sql.js does not use disk-based PRAGMAs like WAL mode
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 30000;         -- 30s wait for locks

Benefits:

  • Data integrity via atomic transactions
  • Referential integrity via foreign keys
  • No filesystem I/O during queries (in-memory)

Foreign Key Constraints

FOREIGN KEY (entry_id) REFERENCES memory_journal(id) ON DELETE CASCADE

Benefits:

  • Referential integrity
  • Automatic cleanup
  • Prevents orphaned records

Transaction Safety

db.run("BEGIN TRANSACTION");
try {
  db.run("INSERT ...");
  db.run("UPDATE ...");
  db.run("COMMIT");
} catch (error) {
  db.run("ROLLBACK");
  throw error;
}

Benefits:

  • ACID compliance
  • Data consistency
  • Error recovery

Data Privacy

Local-Only Storage

No network access:

  • No cloud storage
  • No API calls (except Git/GitHub CLI)
  • No user tracking
  • No analytics

Data location:

  • npm: ~/.memory-journal/memory_journal.db
  • Docker: ./data/memory_journal.db (volume mount)

Git/GitHub Integration

Optional and transparent:

create_entry({
  content: "...",
  auto_context: false, // Disable Git integration
});

Git operations:

  • Read-only (no commits)
  • Local repository info only
  • Optional GitHub CLI (user-controlled)

Data captured:

  • Repository name and path
  • Current branch
  • Latest commit
  • GitHub issues (if gh CLI installed)

Not captured:

  • File contents
  • Uncommitted changes
  • Remote repository data
  • Credentials

Soft Delete Security

Recoverable Deletes

// Soft delete (default)
delete_entry({ entry_id: 42, permanent: false });
// Sets deleted_at timestamp, entry remains in DB

// Permanent delete (explicit)
delete_entry({ entry_id: 42, permanent: true });
// Removes from database completely

Benefits:

  • Accidental deletion recovery
  • Audit trail
  • No data loss

Soft-deleted entries:

  • Excluded from searches
  • Not visible in lists
  • Still in database (for recovery)

Access Control

MCP Client Authentication

Security handled by MCP client:

  • Cursor IDE: Desktop app security
  • Claude Desktop: Desktop app security
  • User controls which servers run

File System Permissions

npm installation:

# Database created with user permissions
chmod 600 ~/.memory-journal/memory_journal.db

Docker installation:

# Volume mount with appropriate permissions
mkdir -m 755 data

Secure Configuration

Environment Variables

Docker environment variables:

{
  "args": [
    "-e",
    "DB_PATH=/app/data/custom.db"
    // NO sensitive data in env vars
    // NO API keys
    // NO credentials
  ]
}

Secrets Management

No secrets required:

  • No API keys
  • No passwords
  • No tokens
  • Optional: GitHub CLI (user manages gh auth)

Security Best Practices

For Users

1. Regular backups:

# Backup database
cp ./data/memory_journal.db ./backups/journal-$(date +%Y%m%d).db

2. Docker image verification:

# Verify SHA digest
docker images --digests | grep memory-journal

# Use SHA-pinned images
docker pull writenotenow/memory-journal-mcp@sha256:...

3. Limit Docker access:

{
  "args": [
    "-v",
    "./data:/app/data", // Only mount data dir
    "--read-only", // Read-only root filesystem
    "--tmpfs",
    "/tmp" // Writable tmp
  ]
}

4. Review logs:

# Check for errors
docker logs <container_id>

For Developers

1. Input validation:

if (content.length > MAX_CONTENT_SIZE) {
  throw new Error(`Content too large: ${content.length} > ${MAX_CONTENT_SIZE}`);
}

2. Parameterized queries:

// Always use ? placeholders
db.run("SELECT * FROM memory_journal WHERE id = ?", [entryId]);

3. Deterministic error handling:

Every tool handler uses formatHandlerError() to guarantee structured responses:

import { formatHandlerError } from "../utils/error-helpers.js";

try {
  // Operation
  return { success: true /* ... */ };
} catch (error) {
  return formatHandlerError(error, "tool_name");
  // → { success: false, error: "tool_name failed: <message>" }
}

Agents always receive { success, error } — no raw exceptions, no silent failures.

4. Least privilege:

// Don't request unnecessary permissions
// Don't access unnecessary files
// Don't make unnecessary network calls

Vulnerability Reporting

Found a security issue?

  1. Do NOT open a public GitHub issue
  2. Use GitHub Security Advisories to report privately
  3. Include:
    • Description of vulnerability
    • Steps to reproduce
    • Potential impact
    • Suggested fix (if any)

We will:

  • Acknowledge within 48 hours
  • Provide fix timeline
  • Credit you (if desired)
  • Publish security advisory

Security Audit

Self-Audit Checklist

  • ✅ Input validation (Zod schemas + size limits)
  • ✅ SQL injection prevention (parameterized queries)
  • ✅ LIKE pattern sanitization (wildcard escaping)
  • ✅ Path traversal protection (backup filenames)
  • ✅ HTTP security headers (CSP, X-Frame-Options, Permissions-Policy, etc.)
  • ✅ HTTP rate limiting (100 req/min)
  • ✅ CORS configuration (configurable origin)
  • ✅ Session timeout management (30 min)
  • ✅ Token scrubbing in error logs
  • ✅ No credentials in code
  • ✅ Non-root Docker execution
  • ✅ Minimal base image (Alpine)
  • ✅ Local-only data storage
  • ✅ Transaction safety (ACID)
  • ✅ Error handling (deterministic {success, error} on every tool via formatHandlerError)
  • ✅ Soft delete option

Threat Model

In Scope

Protected against:

  • SQL injection
  • Resource exhaustion (size limits)
  • Accidental data loss (soft delete)
  • Database integrity (transactions)
  • Container escape (non-root, no privileges)

Out of Scope

Not protected against:

  • Malicious MCP client
  • Compromised host system
  • Physical access to database file
  • User deleting database file

Why:

  • Local-first design trusts the host
  • User has full control
  • Desktop application security model

Compliance

Data Protection

GDPR-friendly:

  • No data collection
  • No data transmission
  • Local storage only
  • User controls all data
  • Easy data export/deletion

No PII collection:

  • No names
  • No email addresses
  • No tracking
  • No analytics

Security Updates

Staying Secure

npm:

# Update to latest version
npm install -g memory-journal-mcp@latest

Docker:

# Pull latest image
docker pull writenotenow/memory-journal-mcp:latest

# Or specific SHA
docker pull writenotenow/memory-journal-mcp@sha256:...

Monitor:

  • GitHub releases
  • Docker Hub updates
  • Security advisories

Next: Check Architecture or Performance.

Clone this wiki locally