project-root-env-var.md•10.2 kB
# 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
```bash
# 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
```bash
# 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
```python
# 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
```bash
# 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:**
```dockerfile
# Set project root for path constraints
ENV BASIC_MEMORY_HOME=/app/data/basic-memory \
    BASIC_MEMORY_PROJECT_ROOT=/app/data
```
**docker-compose.yml:**
```yaml
services:
  basic-memory:
    environment:
      BASIC_MEMORY_HOME: /app/data/basic-memory
      BASIC_MEMORY_PROJECT_ROOT: /app/data
    volumes:
      - ./data:/app/data
```
### Kubernetes Deployment
```yaml
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:**
```bash
# 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:**
```bash
# 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:**
```bash
# 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
```bash
# 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)
```bash
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)
```bash
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
```bash
$ 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
```bash
$ 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**
   ```bash
   export BASIC_MEMORY_PROJECT_ROOT=/app/data
   ```
2. **Move existing projects**
   ```bash
   # Backup first
   cp -r ~/old-project /app/data/old-project
   ```
3. **Update config.json**
   ```bash
   # Edit ~/.basic-memory/config.json
   {
     "projects": {
       "main": "/app/data/basic-memory",
       "old-project": "/app/data/old-project"
     }
   }
   ```
4. **Verify paths**
   ```bash
   bm project list
   # All paths should be under /app/data
   ```
### Disabling PROJECT_ROOT
To remove constraints:
```bash
# 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
```bash
# 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
```bash
# 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
```bash
$ bm project add test /app/data/test
Error: Path must be under /app/data
```
**Solution:** Verify PROJECT_ROOT is correct
```bash
echo $BASIC_MEMORY_PROJECT_ROOT
# Should match expected path
```
### Paths Resolving Incorrectly
**Problem:** Symlinks not working as expected
**Solution:** Check symlink target
```bash
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
```yaml
# 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
```python
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
```python
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