-
Notifications
You must be signed in to change notification settings - Fork 7
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.
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
const MAX_CONTENT_SIZE = 50 * 1024; // 50KB per entry
const MAX_TAG_LENGTH = 100; // 100 characters per tagWhy limits:
- Prevent resource exhaustion
- Ensure reasonable performance
- Protect database integrity
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
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\\%'Backup and restore filenames are validated against path traversal attacks:
// Rejects malicious filenames like "../../etc/passwd"
assertNoPathTraversal(filename); // Throws PathTraversalErrorWhen running in HTTP mode (--transport http), the following security measures apply:
Every HTTP response includes these security headers:
X-Content-Type-Options: nosniffX-Frame-Options: DENYContent-Security-Policy: default-src 'none'; frame-ancestors 'none'Cache-Control: no-store, no-cache, must-revalidateReferrer-Policy: no-referrerPermissions-Policy: camera=(), microphone=(), geolocation=()
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# 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"Opt-in via --enable-hsts flag. When enabled, sends Strict-Transport-Security: max-age=31536000; includeSubDomains on all responses.
Request bodies are limited to 1MB to prevent memory exhaustion.
- UUID-based session IDs via
crypto.randomUUID() - 30-minute timeout with automatic cleanup
- 5-minute sweep interval for expired sessions
# Create non-root user
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser
# Switch to non-root user
USER appuserBenefits:
- Container cannot modify host system
- Reduced attack surface
- Follows least privilege principle
FROM node:24-alpineAlpine Linux benefits:
- Small attack surface (~5MB base)
- Security-focused distribution
- Fewer vulnerabilities
- Fast updates
{
"command": "docker",
"args": [
"run",
"--rm",
"-i",
// NO --privileged flag
// NO --cap-add flags
"-v",
"./data:/app/data",
"writenotenow/memory-journal-mcp:latest"
]
}{
"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
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/tagsBenefits:
- Immutable image reference
- Protection against tag hijacking
- Reproducible deployments
- Supply chain verification
Tag-based (convenience):
docker pull writenotenow/memory-journal-mcp:latestsql.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 locksBenefits:
- Data integrity via atomic transactions
- Referential integrity via foreign keys
- No filesystem I/O during queries (in-memory)
FOREIGN KEY (entry_id) REFERENCES memory_journal(id) ON DELETE CASCADEBenefits:
- Referential integrity
- Automatic cleanup
- Prevents orphaned records
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
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)
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
ghCLI installed)
Not captured:
- File contents
- Uncommitted changes
- Remote repository data
- Credentials
// 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 completelyBenefits:
- Accidental deletion recovery
- Audit trail
- No data loss
Soft-deleted entries:
- Excluded from searches
- Not visible in lists
- Still in database (for recovery)
Security handled by MCP client:
- Cursor IDE: Desktop app security
- Claude Desktop: Desktop app security
- User controls which servers run
npm installation:
# Database created with user permissions
chmod 600 ~/.memory-journal/memory_journal.dbDocker installation:
# Volume mount with appropriate permissions
mkdir -m 755 dataDocker environment variables:
{
"args": [
"-e",
"DB_PATH=/app/data/custom.db"
// NO sensitive data in env vars
// NO API keys
// NO credentials
]
}No secrets required:
- No API keys
- No passwords
- No tokens
- Optional: GitHub CLI (user manages
gh auth)
1. Regular backups:
# Backup database
cp ./data/memory_journal.db ./backups/journal-$(date +%Y%m%d).db2. 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>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 callsFound a security issue?
- Do NOT open a public GitHub issue
- Use GitHub Security Advisories to report privately
- 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
- ✅ 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 viaformatHandlerError) - ✅ Soft delete option
Protected against:
- SQL injection
- Resource exhaustion (size limits)
- Accidental data loss (soft delete)
- Database integrity (transactions)
- Container escape (non-root, no privileges)
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
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
npm:
# Update to latest version
npm install -g memory-journal-mcp@latestDocker:
# 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.