feat: introduce BASIC_MEMORY_PROJECT_ROOT for path constraints by phernandez · Pull Request #334 · basicmachines-co/basic-memory · GitHub
Skip to content

feat: introduce BASIC_MEMORY_PROJECT_ROOT for path constraints - #334

Merged
phernandez merged 3 commits into
mainfrom
bm-issue-333
Oct 5, 2025
Merged

feat: introduce BASIC_MEMORY_PROJECT_ROOT for path constraints#334
phernandez merged 3 commits into
mainfrom
bm-issue-333

Conversation

@phernandez

Copy link
Copy Markdown
Member

Summary

Introduces BASIC_MEMORY_PROJECT_ROOT environment variable to replace cloud mode path coupling and fixes ConfigManager to properly respect environment variable overrides.

Fixes #333

Problem

  1. BASIC_MEMORY_HOME had dual, conflicting semantics:

    • Path to the "main" (default) project directory
    • Root directory where ALL projects must be created (in cloud mode)
  2. Environment variables didn't override config file values:

    • ConfigManager loaded config from file with explicit values
    • This prevented Pydantic's env var override mechanism from working
    • Tests required complex mocking to set environment variables
  3. Cloud mode tightly coupled to path validation:

    • Path constraints were tied to BASIC_MEMORY_CLOUD_MODE
    • Not flexible for other constrained environments (Docker, multi-tenant)

Solution

1. ConfigManager Refactor (Commit 1)

Fixed environment variable override behavior:

  • Added module-level _CONFIG_CACHE for performance across instances
  • Env vars now properly override file config values (Pydantic best practices)
  • Cache invalidated on save_config() to ensure consistency
  • Follows proper Pydantic Settings design pattern

Benefits:

  • ✅ Environment variables work as expected
  • ✅ Better performance (cached config across ConfigManager() instances)
  • ✅ Simplified testing (no complex mocking needed)

2. BASIC_MEMORY_PROJECT_ROOT (Commit 2)

Introduced new environment variable:

  • BASIC_MEMORY_PROJECT_ROOT: If set, all projects must be underneath this directory
  • BASIC_MEMORY_HOME: Location of the "main" project (unchanged, backward compatible)

Implementation:

  • Added project_root field to BasicMemoryConfig (config.py:106-110)
  • Updated project_service.py to check project_root instead of cloud_mode_enabled
  • Same path sanitization logic (strips /, ~/, ../)
  • Validates resolved paths stay within boundary
  • Clear error messages reference BASIC_MEMORY_PROJECT_ROOT

Tests:

  • Renamed cloud mode tests to project_root tests
  • Simplified using improved ConfigManager (just set env var + invalidate cache)
  • No complex mocking needed anymore
  • All tests passing on Ubuntu (skipped on Windows - cloud runs on Linux only)

Benefits:

  • ✅ Decouples path restriction from cloud mode
  • ✅ Useful for any constrained environment (cloud, docker, multi-tenant)
  • ✅ Backward compatible - existing users unaffected
  • ✅ More explicit and clear naming
  • ✅ BASIC_MEMORY_HOME keeps its original purpose

3. Dockerfile & Test Updates (Commit 3)

Updated Dockerfile:

ENV BASIC_MEMORY_HOME=/app/data/basic-memory \
    BASIC_MEMORY_PROJECT_ROOT=/app/data

Directory structure:

/app/data/
  ├── basic-memory/  (main project)
  │   └── *.md
  └── other-project/ (optional additional projects)

Test fixtures:

  • Added config cache invalidation in config_manager fixture
  • Ensures each test starts with clean config state
  • Prevents test pollution from cached config values

Example Usage

Cloud Mode

BASIC_MEMORY_HOME=/app/data/basic-memory  # main project
BASIC_MEMORY_PROJECT_ROOT=/app/data       # all projects under /app/data

Results:

  • Input: /tmp/test/app/data/tmp/test (sanitized)
  • Input: test/app/data/test
  • Main project: /app/data/basic-memory/ (unchanged)

Local Mode with Constraints

BASIC_MEMORY_HOME=~/basic-memory          # main project
BASIC_MEMORY_PROJECT_ROOT=~/my-projects   # all projects under here

Local Mode Unrestricted (Default)

BASIC_MEMORY_HOME=~/basic-memory          # main project
# BASIC_MEMORY_PROJECT_ROOT not set - projects can be anywhere

Files Changed

  • src/basic_memory/config.py - Added project_root field, module-level caching, env var override
  • src/basic_memory/services/project_service.py - Use project_root instead of cloud_mode_enabled
  • tests/services/test_project_service.py - Simplified cloud mode tests
  • tests/conftest.py - Added cache invalidation
  • Dockerfile - Set both env vars for clean project structure

Test Plan

  • Run all tests: make check
  • Config tests pass: pytest tests/test_config.py -v
  • Project service tests pass: pytest tests/services/test_project_service.py -v
  • Project root tests pass (sanitization, security, backward compat)
  • Code formatted and linted
  • Type checking passes

Migration Notes

For existing users:

  • No changes needed - fully backward compatible
  • BASIC_MEMORY_HOME continues to work as before

For cloud deployments:

  • Update Dockerfile or deployment config to set BASIC_MEMORY_PROJECT_ROOT=/app/data
  • BASIC_MEMORY_CLOUD_MODE can remain for other cloud-specific behaviors

For basic-memory-cloud:

Breaking Changes

None - this is fully backward compatible.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

- Add module-level caching for configuration (_CONFIG_CACHE)
- Environment variables now properly override file config values
- Follows Pydantic Settings best practices for env var precedence
- Cache invalidated on save_config() to ensure consistency
- Improves performance by avoiding repeated file reads

This fixes the issue where setting env vars like BASIC_MEMORY_CLOUD_MODE
didn't work because explicit values from config file took precedence.

The new flow:
1. Check cache first
2. Load from file
3. Create env-based config
4. Merge: file data as base, env vars override
5. Cache and return

Resolves testing issues where monkeypatching env vars didn't work.

Signed-off-by: phernandez <paul@basicmachines.co>
Replaces cloud mode coupling with explicit project root constraint.

Fixes #333

## Problem

BASIC_MEMORY_HOME had dual, conflicting semantics:
1. Path to the "main" (default) project directory
2. Root directory where ALL projects must be created (cloud mode)

This coupling to cloud mode was inflexible and confusing.

## Solution

Introduce BASIC_MEMORY_PROJECT_ROOT environment variable:
- If set: ALL projects must be created underneath this directory
- If not set: Projects can be created anywhere (default behavior)

## Changes

### Config (config.py:106-110)
- Added project_root field to BasicMemoryConfig
- Automatically populated from BASIC_MEMORY_PROJECT_ROOT env var
- Optional field (defaults to None)

### Service Layer (project_service.py:102-126)
- Changed from checking cloud_mode_enabled to checking project_root
- Same path sanitization logic (strips /, ~/, ../)
- Validates resolved paths stay within project_root boundary
- Clear error messages reference BASIC_MEMORY_PROJECT_ROOT

### Tests (test_project_service.py:719-869)
- Renamed cloud mode tests to project_root tests
- Simplified tests using improved ConfigManager
- No complex mocking needed - just set env var and invalidate cache
- test_add_project_with_project_root_sanitizes_paths
- test_add_project_with_project_root_rejects_escape_attempts
- test_add_project_without_project_root_allows_arbitrary_paths

## Benefits

✅ Decouples path restriction from cloud mode
✅ Useful for any constrained environment (cloud, docker, multi-tenant)
✅ Backward compatible - existing users unaffected
✅ More explicit and clear naming
✅ BASIC_MEMORY_HOME remains for main project location

## Example Usage

### Cloud Mode
```bash
BASIC_MEMORY_HOME=/app/data/basic-memory  # main project
BASIC_MEMORY_PROJECT_ROOT=/app/data       # all projects under /app/data
```

### Local Constrained Mode
```bash
BASIC_MEMORY_HOME=~/basic-memory          # main project
BASIC_MEMORY_PROJECT_ROOT=~/my-projects   # all projects constrained
```

### Local Unrestricted (Default)
```bash
BASIC_MEMORY_HOME=~/basic-memory          # main project
# BASIC_MEMORY_PROJECT_ROOT not set - projects anywhere
```

Signed-off-by: phernandez <paul@basicmachines.co>
Updates containerized deployment to use both environment variables
for clean project organization and path constraints.

## Dockerfile Changes

- Set BASIC_MEMORY_HOME=/app/data/basic-memory (main project location)
- Set BASIC_MEMORY_PROJECT_ROOT=/app/data (constrain all projects to volume)
- Create /app/data/basic-memory directory

Benefits:
- Main project cleanly separated in /app/data/basic-memory/
- All projects constrained to /app/data/ (volume mount)
- Users can't accidentally create projects outside mounted volume
- Consistent with cloud deployment pattern

Directory structure:
/app/data/
  ├── basic-memory/  (main project)
  │   └── *.md
  └── other-project/ (optional additional projects)

## Test Fixture Changes

- Add config cache invalidation in config_manager fixture
- Ensures each test starts with clean config state
- Prevents test pollution from cached config values

Signed-off-by: phernandez <paul@basicmachines.co>
@github-actions

github-actions Bot commented Oct 5, 2025

Copy link
Copy Markdown
Contributor

@phernandez
phernandez merged commit ccc4386 into main Oct 5, 2025
16 checks passed
@phernandez
phernandez deleted the bm-issue-333 branch October 5, 2025 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Introduce BASIC_MEMORY_PROJECT_ROOT to replace cloud mode path constraints

1 participant