Files
basicmachines-co-basic-memory/v15-docs/project-root-env-var.md
T
2025-10-09 19:09:47 -05:00

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:

  1. All project paths are validated against the root
  2. Paths are sanitized to prevent directory traversal
  3. Symbolic links are resolved and verified
  4. 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:

  1. Choose project root location

    export BASIC_MEMORY_PROJECT_ROOT=/app/data
    
  2. Move existing projects

    # Backup first
    cp -r ~/old-project /app/data/old-project
    
  3. Update config.json

    # Edit ~/.basic-memory/config.json
    {
      "projects": {
        "main": "/app/data/basic-memory",
        "old-project": "/app/data/old-project"
      }
    }
    
  4. 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

  1. Always set in production: Use PROJECT_ROOT in deployed environments
  2. Minimal permissions: Set directory permissions to 700 or 750
  3. Audit project creation: Log all project add/remove operations
  4. Regular validation: Periodically check project paths haven't escaped
  5. 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 location
  • env-var-overrides.md - Environment variable precedence
  • Docker deployment guide
  • Security best practices