The Memory Journal MCP server implements comprehensive security measures to protect your personal journal data.
The server uses the native better-sqlite3 driver with sqlite-vec for vector operations, running directly against the filesystem.
- ✅ PRAGMA foreign_keys = ON — enforces referential integrity and
ON DELETE CASCADE - ✅ Parameterized queries — all user input bound via
?placeholders - ✅ WAL journal mode — high concurrency with non-blocking reads (
PRAGMA journal_mode = WAL) - ✅ Synchronous Normal — optimized durability and performance (
PRAGMA synchronous = NORMAL)
- ✅ Data directory:
700(full access for owner only) in Docker - ✅ Non-root user (
appuser:appgroup) owns data directory
- Journal entries: 50,000 characters maximum
- Tags: 100 characters maximum
- Entry types: 50 characters maximum
- Significance types: 50 characters maximum
- HTTP request body: 1MB maximum (prevents memory exhaustion)
Tags are stored as-is via parameterized queries. Special characters in tags are safely handled by the database layer and do not pose injection risks.
- ✅ No Raw SQL Execution — All raw SQL methods (
_executeRawQueryUnsafe) have been removed. All handlers must use strict, parameterized methods defined byIDatabaseAdapter. - ✅ Parameterized queries used throughout
- ✅ Input validation via Zod schemas before database operations
- ✅ Warning system for potentially dangerous content patterns
- ✅ FTS5 / LIKE pattern sanitization (escapes
%,_,\wildcards and handles FTS5 syntax errors gracefully) - ✅ Date format whitelisting (prevents strftime injection)
- ✅ Explicit Filesystem Boundaries — Filesystem access MUST be explicitly granted via
ALLOWED_IO_ROOTS. The server operates in a fail-closed mode (ALLOWED_IO_ROOTS=[]) by default if not explicitly configured, preventing all filesystem interaction. - ✅ Implicit Authority Disabled — The server no longer derives ambient filesystem authority from the location of database files.
- ✅ Backup filenames validated - rejects
/,\,..in paths - ✅ Typed security errors with consistent error codes
When running in HTTP mode (--transport http), the following security measures apply:
- ✅ Configurable multiple origins via comma-separated
--cors-originflag orMCP_CORS_ORIGINenvironment variable - ✅ Exact-match verification (no wildcard matching for custom domains)
- 🔒 Default: None (no external origins allowed by default). You MUST explicitly configure CORS for browsers.
- 🔒 Required: Set specific origins for production deployments, or wildcard (
*) will only be accepted if HTTP Authentication is enabled.
# Restrict CORS to specific origins
memory-journal-mcp --transport http --cors-origin "http://localhost:3000,https://my-app.com"
# Or via environment variable
export MCP_CORS_ORIGIN="http://localhost:3000,https://my-app.com"- ✅ DNS Rebinding Protection —
hostHeaderValidationmiddleware prevents CVE-2025-66414 - ✅ Strict-Transport-Security (HSTS) — max-age=31536000; includeSubDomains (opt-in via
--enable-hsts). Note: Only effective when deployed behind a reverse proxy handling HTTPS. - ✅ X-Content-Type-Options: nosniff — prevents MIME sniffing
- ✅ X-Frame-Options: DENY — prevents clickjacking
- ✅ Content-Security-Policy: default-src 'none'; frame-ancestors 'none' — prevents XSS and framing
- ✅ Cache-Control: no-store, no-cache, must-revalidate — prevents caching of sensitive journal data
- ✅ Referrer-Policy: no-referrer — prevents referrer leakage
⚠️ CORS wildcard warning — server logs a warning when CORS origin is*
- ✅ Built-in Rate Limiting — 100 requests/minute per IP (sliding window with
Retry-Afterheader) - ✅ HTTP Timeouts — Request timeout (120s), keep-alive timeout (65s), headers timeout (66s)
- ✅ UUID-based session IDs (cryptographically random)
- ✅ 30-minute session timeout - idle sessions automatically expired
- ✅ 5-minute sweep interval - periodic cleanup of abandoned sessions
- ✅ Explicit session termination via
DELETE /mcp
- ✅ 1MB body limit on JSON requests (prevents memory exhaustion DoS)
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 |
| 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 |
# 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- Client sends
Authorization: Bearer <token>header - Server validates JWT signature against JWKS endpoint
- Server checks issuer, audience, and expiration claims
- Server extracts scopes from token and enforces tool-level access
- Invalid/missing tokens receive
401withWWW-Authenticateheader
When OAuth is not enabled, authentication falls back to:
- Simple token auth — if
MCP_AUTH_TOKENenv var is set, requests must includeAuthorization: Bearer <token>matching the configured value - No auth — if no auth mechanism is configured, all requests are accepted (suitable for local/development use)
- ✅ Environment variables only - tokens never stored in config files
- ✅ Error message scrubbing - Authorization headers stripped from error logs
- ✅ Optional integration - server works fully offline without GitHub token
- ✅ Minimal scopes - only requires
repo,project,read:org
# Required for Authentication (Must not be the placeholder token)
MCP_AUTH_TOKEN="your-secure-token-here"
# Required for Filesystem Tools (Export, Backup)
ALLOWED_IO_ROOTS="/path/to/allow1,/path/to/allow2"
# Required for GitHub features
GITHUB_TOKEN=ghp_... # GitHub personal access token
# Optional
GITHUB_ORG_TOKEN=ghp_... # For organization projects
PROJECT_REGISTRY='{"my-repo":{"path":"/path/to/repo","project_number":1}}' # Multi-project routing
DEFAULT_PROJECT_NUMBER=1 # Default project for issue assignment
MCP_CORS_ORIGIN= # CORS origin (default: none)
MCP_HOST=localhost # Server bind host
AUTO_REBUILD_INDEX=true # Rebuild vector index on startup- ✅ Dedicated user:
appuser(UID 1001) with minimal privileges - ✅ Restricted group:
appgroup(GID 1001) - ✅ Restricted data directory:
700permissions
- ✅ Minimal base image:
node:26-alpine - ✅ Multi-stage build: Build dependencies not in production image
- ✅ Process isolation from host system
- ✅ No shell access needed for production
# Secure volume mounting
docker run -v ./data:/app/data:rw,noexec,nosuid,nodev memory-journal-mcp# Apply resource limits
docker run --memory=1g --cpus=1 memory-journal-mcpRecommended (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:latestCode Mode (mj_execute_code) executes user-provided JavaScript in a hardened worker_threads + vm.createContext sandbox:
- ✅ V8 code generation disabled —
codeGeneration: { strings: false, wasm: false }preventseval()andFunction()construction from strings at the V8 engine level - ✅ Separate V8 isolate — each worker thread runs in its own V8 instance with enforced heap limits (
maxOldGenerationSizeMb,maxYoungGenerationSizeMb) - ✅ Frozen prototypes — all built-in prototypes (Object, Function, Array, Error, Map, Set, Promise, etc.) frozen inside the vm context to prevent dynamic constructor chain escapes
- ✅ Proxy constructor nullified —
Proxy: undefinedin the sandbox context prevents meta-object protocol abuse
- ✅ 18 blocked patterns — regex rules blocking
require(),import(),process.*,global.*,eval(),Function(),__proto__,constructor.constructor,['constructor'],Reflect.*,Symbol.*,new Proxy(),child_process,fs.*,net.*,http.*,https.* - ✅ 50KB code size limit — prevents payload-based resource exhaustion
- ✅ RPC allowlist — host-side validation prevents workers from invoking unauthorized API methods
- ✅ Rate limiting — 60 executions per minute per client
- ✅ Hard timeouts — configurable execution limit (default 30s) with forced worker termination
- ✅ Egress boundary enforcement — result serialization capped at configurable limit (default 100KB) to prevent OOM via oversized payloads
- ✅ Readonly Proxy traps — when
readonly: true, stripped mutation methods throw clear error messages listing available methods - ✅ Schema Introspection Scope —
.schema()properties are strictly bound to the permitted methods in the RPC allowlist, preventing unintended internal structure leakage - ✅ Audit logging — all executions logged with code preview, metrics, and readonly mode
- ✅ Full data ownership: SQLite database stays on your machine
- ✅ No telemetry: No data sent to external telemetry endpoints
⚠️ External Services: If configured, communicates with GitHub API and fetches OAuth discovery (JWKS) endpoints.⚠️ Semantic search: ML models are executed locally via@huggingface/transformers, but the model weights are downloaded from the Hugging Face registry on first run if not cached locally.
- ✅ Git context: Only reads local repository information
- ✅ No sensitive data: Doesn't access private keys or credentials
- ✅ Optional GitHub integration: Only if explicitly configured with token
// 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 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
npm installation:
# Database created with user permissions
chmod 600 ~/.memory-journal/memory_journal.dbDocker installation:
# Volume mount with appropriate permissions
mkdir -m 755 dataIf 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-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)
- ✅ CodeQL analysis - automated static analysis on push/PR
- ✅ Trivy container scanning - Docker image vulnerability detection
- ✅ TruffleHog - secret scanning on push/PR
- ✅ npm audit - dependency vulnerability checking
- Set a CORS origin when exposing the HTTP transport on a network
- Keep Node.js updated: Use Node.js 26+ (LTS)
- Secure host system: Ensure your host machine is secure
- Regular backups: Use the
backup_journaltool or back up your.dbfile - Limit network access: Don't expose the HTTP transport to untrusted networks
- Use resource limits: Apply Docker
--memoryand--cpuslimits
- Regular updates: Keep Node.js and npm dependencies updated
- Security scanning: Regularly scan Docker images for vulnerabilities
- Code review: All database operations use parameterized queries
- Input validation: All tool inputs validated via Zod schemas
- Foreign key enforcement (
PRAGMA foreign_keys = ON) - Input validation and length limits (Zod schemas)
- Parameterized SQL queries
- SQL injection detection heuristics (defense-in-depth)
- Path traversal protection (
assertNoPathTraversal) - Explicit Filesystem Boundaries (
ALLOWED_IO_ROOTSenforcement) - Strict Authentication Tokens (Placeholder rejection)
- FTS5 / LIKE pattern sanitization (
sanitizeSearchQuery) - Date format whitelisting (
validateDateFormatPattern) - HTTP body size limit (1MB)
- Configurable CORS multi-origin with exact-match enforcement
- HTTP timeouts and built-in rate limiter (100 req/min)
- DNS rebinding protection and strict HSTS
- Security headers (CSP, X-Content-Type-Options, X-Frame-Options, Cache-Control, Referrer-Policy, Permissions-Policy)
- Session timeout (30 minutes)
- Non-root Docker user
- Multi-stage Docker build
- Local-first data architecture
- GitHub token error scrubbing
- Code Mode sandbox isolation (worker_threads V8 isolate + vm.createContext)
- Code Mode V8 codeGeneration restrictions (eval/Function disabled at engine level)
- Code Mode frozen built-in prototypes (constructor chain escape prevention)
- Code Mode blocked patterns (18 static regex rules)
- Code Mode Proxy constructor nullified in sandbox context
- Code Mode RPC allowlist validation (host-side method authorization)
- Code Mode readonly Proxy traps (structured errors for stripped methods)
- CI/CD security pipeline (CodeQL, Trivy, secret scanning)
- Comprehensive security documentation
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
| Component | Supported Versions | Notes |
|---|---|---|
| Node.js | 26.x (LTS) | Minimum required version for worker_threads and isolation features. |
| MCP Protocol | 2024-11-05 | Full compatibility with the official Model Context Protocol. |
| Docker Base | node:26-alpine |
Alpine-based images receive regular vulnerability patches. |
If you discover a security vulnerability, please:
- Do not open a public GitHub issue
- Contact the maintainers privately
- Provide detailed information about the vulnerability
- Allow time for the issue to be addressed before public disclosure
- Container updates: Rebuild Docker images when base images are updated
- Dependency updates: Keep npm packages updated via manual update workflows
- Database maintenance: Run
ANALYZEandPRAGMA optimizeregularly - Security patches: Apply host system security updates
The Memory Journal MCP server is designed with security-first principles to protect your personal journal data while maintaining excellent performance and usability.