Signed-off-by: phernandez <paul@basicmachines.co> Co-authored-by: Claude <noreply@anthropic.com>
9.9 KiB
BASIC_MEMORY_PROJECT_ROOT Environment Variable
Status: New Feature PR: #334 Use Case: Security, containerization, path constraints
What's New
v0.15.0 introduces the BASIC_MEMORY_PROJECT_ROOT environment variable to constrain all project paths to a specific directory. This provides security and enables safe multi-tenant deployments.
Quick Examples
Containerized Deployment
# Docker/containerized environment
export BASIC_MEMORY_PROJECT_ROOT=/app/data
export BASIC_MEMORY_HOME=/app/data/basic-memory
# All projects must be under /app/data
bm project add my-project /app/data/my-project # ✓ Allowed
bm project add my-project /tmp/unsafe # ✗ Blocked
Development Environment
# Local development - no constraint (default)
# BASIC_MEMORY_PROJECT_ROOT not set
# Projects can be anywhere
bm project add work ~/Documents/work-notes # ✓ Allowed
bm project add personal ~/personal-kb # ✓ Allowed
How It Works
Path Validation
When BASIC_MEMORY_PROJECT_ROOT is set:
- All project paths are validated against the root
- Paths are sanitized to prevent directory traversal
- Symbolic links are resolved and verified
- Escape attempts are blocked (e.g.,
../../../etc)
Path Sanitization
# Example internal validation
project_root = "/app/data"
user_path = "/app/data/../../../etc"
# Sanitized and validated
resolved_path = Path(user_path).resolve()
# → "/etc"
# Check if under project_root
if not str(resolved_path).startswith(project_root):
raise ValueError("Path must be under /app/data")
Configuration
Set via Environment Variable
# In shell or .bashrc/.zshrc
export BASIC_MEMORY_PROJECT_ROOT=/app/data
# Or in Docker
docker run -e BASIC_MEMORY_PROJECT_ROOT=/app/data ...
Docker Deployment
Dockerfile:
# Set project root for path constraints
ENV BASIC_MEMORY_HOME=/app/data/basic-memory \
BASIC_MEMORY_PROJECT_ROOT=/app/data
docker-compose.yml:
services:
basic-memory:
environment:
BASIC_MEMORY_HOME: /app/data/basic-memory
BASIC_MEMORY_PROJECT_ROOT: /app/data
volumes:
- ./data:/app/data
Kubernetes Deployment
apiVersion: v1
kind: Pod
spec:
containers:
- name: basic-memory
env:
- name: BASIC_MEMORY_PROJECT_ROOT
value: "/app/data"
- name: BASIC_MEMORY_HOME
value: "/app/data/basic-memory"
volumeMounts:
- name: data-volume
mountPath: /app/data
Use Cases
1. Container Security
Problem: Containers shouldn't create projects outside mounted volumes
Solution:
# Set project root to volume mount
export BASIC_MEMORY_PROJECT_ROOT=/app/data
# Projects confined to volume
bm project add notes /app/data/notes # ✓
bm project add evil /etc/passwd # ✗ Blocked
2. Multi-Tenant SaaS
Problem: Tenant A shouldn't access Tenant B's files
Solution:
# Per-tenant isolation
export BASIC_MEMORY_PROJECT_ROOT=/app/data/tenant-${TENANT_ID}
# Tenant can only create projects under their directory
bm project add my-notes /app/data/tenant-123/notes # ✓
bm project add sneaky /app/data/tenant-456/notes # ✗ Blocked
3. Shared Hosting
Problem: Users need isolated project spaces
Solution:
# Per-user isolation
export BASIC_MEMORY_PROJECT_ROOT=/home/${USER}/basic-memory
# User confined to their home directory
bm project add personal /home/alice/basic-memory/personal # ✓
bm project add other /home/bob/basic-memory/data # ✗ Blocked
Relationship with BASIC_MEMORY_HOME
BASIC_MEMORY_HOME and BASIC_MEMORY_PROJECT_ROOT serve different purposes:
| Variable | Purpose | Default | Example |
|---|---|---|---|
BASIC_MEMORY_HOME |
Default project location | ~/basic-memory |
Where "main" project lives |
BASIC_MEMORY_PROJECT_ROOT |
Path constraint boundary | None (unrestricted) | Security boundary |
Using Both Together
# Typical containerized setup
export BASIC_MEMORY_PROJECT_ROOT=/app/data # Constraint: all under /app/data
export BASIC_MEMORY_HOME=/app/data/basic-memory # Default: main project location
# This creates main project at /app/data/basic-memory
# And ensures all other projects are also under /app/data
Key Differences
BASIC_MEMORY_HOME:
- Sets default project path
- Used for "main" project
- Does NOT enforce constraints
- Optional - defaults to
~/basic-memory
BASIC_MEMORY_PROJECT_ROOT:
- Enforces path constraints
- Validates ALL project paths
- Prevents path traversal
- Optional - if not set, no constraints
Validation Examples
Valid Paths (with PROJECT_ROOT=/app/data)
export BASIC_MEMORY_PROJECT_ROOT=/app/data
# Direct child
bm project add notes /app/data/notes # ✓
# Nested child
bm project add work /app/data/projects/work # ✓
# Relative path (resolves to /app/data/relative)
bm project add rel /app/data/relative # ✓
# Symlink (resolves under /app/data)
ln -s /app/data/real /app/data/link
bm project add linked /app/data/link # ✓
Invalid Paths (with PROJECT_ROOT=/app/data)
export BASIC_MEMORY_PROJECT_ROOT=/app/data
# Path traversal attempt
bm project add evil /app/data/../../../etc
# ✗ Error: Path must be under /app/data
# Absolute path outside root
bm project add outside /tmp/data
# ✗ Error: Path must be under /app/data
# Symlink escaping root
ln -s /etc/passwd /app/data/evil
bm project add bad /app/data/evil
# ✗ Error: Path must be under /app/data
# Relative path escaping
bm project add sneaky /app/data/../../sneaky
# ✗ Error: Path must be under /app/data
Error Messages
Path Outside Root
$ bm project add test /tmp/test
Error: BASIC_MEMORY_PROJECT_ROOT is set to /app/data.
All projects must be created under this directory.
Invalid path: /tmp/test
Escape Attempt Blocked
$ bm project add evil /app/data/../../../etc
Error: BASIC_MEMORY_PROJECT_ROOT is set to /app/data.
All projects must be created under this directory.
Invalid path: /etc
Migration Guide
Enabling PROJECT_ROOT on Existing Setup
If you have existing projects outside the desired root:
-
Choose project root location
export BASIC_MEMORY_PROJECT_ROOT=/app/data -
Move existing projects
# Backup first cp -r ~/old-project /app/data/old-project -
Update config.json
# Edit ~/.basic-memory/config.json { "projects": { "main": "/app/data/basic-memory", "old-project": "/app/data/old-project" } } -
Verify paths
bm project list # All paths should be under /app/data
Disabling PROJECT_ROOT
To remove constraints:
# Unset environment variable
unset BASIC_MEMORY_PROJECT_ROOT
# Or remove from Docker/config
# Now projects can be created anywhere again
Testing Path Constraints
Verify Configuration
# Check if PROJECT_ROOT is set
env | grep BASIC_MEMORY_PROJECT_ROOT
# Try creating project outside root (should fail)
bm project add test /tmp/test
Docker Testing
# Run with constraint
docker run \
-e BASIC_MEMORY_PROJECT_ROOT=/app/data \
-v $(pwd)/data:/app/data \
basic-memory:latest \
bm project add notes /app/data/notes
# Verify in container
docker exec -it container_id env | grep PROJECT_ROOT
Security Best Practices
- Always set in production: Use PROJECT_ROOT in deployed environments
- Minimal permissions: Set directory permissions to 700 or 750
- Audit project creation: Log all project add/remove operations
- Regular validation: Periodically check project paths haven't escaped
- Volume mounts: Ensure PROJECT_ROOT matches Docker volume mounts
Troubleshooting
Projects Not Creating
Problem: Can't create projects with PROJECT_ROOT set
$ bm project add test /app/data/test
Error: Path must be under /app/data
Solution: Verify PROJECT_ROOT is correct
echo $BASIC_MEMORY_PROJECT_ROOT
# Should match expected path
Paths Resolving Incorrectly
Problem: Symlinks not working as expected
Solution: Check symlink target
ls -la /app/data/link
# → /app/data/link -> /some/target
# Ensure target is under PROJECT_ROOT
realpath /app/data/link
Docker Volume Issues
Problem: PROJECT_ROOT doesn't match volume mount
Solution: Align environment and volume
# docker-compose.yml
environment:
BASIC_MEMORY_PROJECT_ROOT: /app/data # ← Must match volume mount
volumes:
- ./data:/app/data # ← Mount point
Implementation Details
Path Sanitization Algorithm
def sanitize_and_validate_path(path: str, project_root: str) -> str:
"""Sanitize path and validate against project root."""
# Convert to absolute path
base_path = Path(project_root).resolve()
target_path = Path(path).resolve()
# Get as POSIX string for comparison
resolved_path = target_path.as_posix()
base_posix = base_path.as_posix()
# Verify resolved path is under project_root
if not resolved_path.startswith(base_posix):
raise ValueError(
f"BASIC_MEMORY_PROJECT_ROOT is set to {project_root}. "
f"All projects must be created under this directory. "
f"Invalid path: {path}"
)
return resolved_path
Config Loading
class BasicMemoryConfig(BaseSettings):
project_root: Optional[str] = Field(
default=None,
description="If set, all projects must be created underneath this directory"
)
model_config = SettingsConfigDict(
env_prefix="BASIC_MEMORY_", # Maps BASIC_MEMORY_PROJECT_ROOT
extra="ignore",
)
See Also
basic-memory-home.md- Default project locationenv-var-overrides.md- Environment variable precedence- Docker deployment guide
- Security best practices