Skip to content

Security

Chris & Mike edited this page Apr 21, 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

Filesystem IO Boundaries

Filesystem operations via Code Mode (mj_execute_code) and data export tools are hard-blocked by default to prevent unauthorized access. Administrators must explicitly configure the ALLOWED_IO_ROOTS environment variable with absolute paths to grant access.

# Critical Security Boundary: Comma-separated absolute paths granting filesystem access
export ALLOWED_IO_ROOTS="/path/to/your/git/repo,/path/to/shared/data"

Security Warning: Never use generic directories like / or /usr for ALLOWED_IO_ROOTS.


🌐 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

🔐 OAuth 2.1 Authentication

For production HTTP deployments, Memory Journal supports OAuth 2.1 authentication (opt-in):

Component Status Description
Protected Resource Metadata RFC 9728 /.well-known/oauth-protected-resource
Auth Server Discovery RFC 8414 metadata discovery with caching
Token Validation JWT validation with JWKS support (via jose)
Scope Enforcement Granular read, write, admin scopes
HTTP Middleware Bearer token extraction and validation

Supported Scopes

Scope Tool Groups Description
read core, search, analytics, relationships, export Read-only operations
write github, team (+ all read groups) Read + write operations
admin admin, backup, codemode (+ all write/read groups) Full administrative access

Enabling OAuth

# CLI flags
memory-journal-mcp --transport http --port 3000 \
  --oauth-enabled \
  --oauth-issuer https://auth.example.com/realms/mcp \
  --oauth-audience memory-journal-mcp \
  --oauth-jwks-uri https://auth.example.com/realms/mcp/protocol/openid-connect/certs

# Or via environment variables
export OAUTH_ENABLED=true
export OAUTH_ISSUER=https://auth.example.com/realms/mcp
export OAUTH_AUDIENCE=memory-journal-mcp
export OAUTH_JWKS_URI=https://auth.example.com/realms/mcp/protocol/openid-connect/certs

How It Works

  1. Client sends Authorization: Bearer <token> header
  2. Server validates JWT signature against JWKS endpoint
  3. Server checks issuer, audience, and expiration claims
  4. Server extracts scopes from token and enforces tool-level access
  5. Invalid/missing tokens receive 401 with WWW-Authenticate header

Fallback Behavior

When OAuth is not enabled, authentication falls back to:

  1. Simple token auth — if MCP_AUTH_TOKEN env var is set, requests must include Authorization: Bearer <token> matching the configured value
  2. No auth — if no auth mechanism is configured, all requests are accepted (suitable for local/development use)

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

better-sqlite3 Native Model

better-sqlite3 operates as a native file-based database with WAL mode for concurrent read access.

-- better-sqlite3 uses native file-based database
PRAGMA journal_mode = WAL;           -- Write-Ahead Logging for concurrent reads
PRAGMA synchronous = NORMAL;         -- Balanced durability/performance
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 30000;         -- 30s wait for locks

Benefits:

  • Data integrity via atomic transactions
  • Referential integrity via foreign keys
  • WAL mode for concurrent read access
  • Native file-based persistence

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 at multiple levels:

  • OAuth 2.1 (HTTP transport, opt-in) - RFC-compliant JWT validation with scope enforcement
  • Simple token auth (HTTP transport, via MCP_AUTH_TOKEN) - Basic Bearer token verification
  • MCP client (stdio transport) - Desktop app security (Cursor IDE, Claude Desktop)
  • 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

Team Database Security

If using team collaboration, the TEAM_DB_PATH dictates where the shared database is located.

Security Warning: Because TEAM_DB_PATH is accessed by multiple users or processes, it must be placed on a secure shared volume with strict access controls to prevent unauthorized data access or modification.

# Set secure permissions on the team database directory
chmod 700 /shared/team-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 formatHandlerErrorResponse() to guarantee structured responses:

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

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

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
  • ✅ OAuth 2.1 authentication (RFC 9728/8414, opt-in)
  • ✅ JWT validation with JWKS caching
  • ✅ Scope-based access control (read/write/admin)
  • ✅ No credentials in code
  • ✅ Non-root Docker execution
  • ✅ Minimal base image (Alpine)
  • ✅ Local-only data storage
  • ✅ Transaction safety (ACID)
  • ✅ Error handling (deterministic {success, error, code, category, suggestion, recoverable} on every tool via formatHandlerErrorResponse)
  • ✅ 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