Skip to the content.
# Multi-Machine Cache Synchronization (v0.3.1+)

## Overview

Bastion implements a stable machine UUID system to safely synchronize encrypted caches across multiple machines while preventing accidental key mismatches.

## Machine UUID

### What It Is

A stable, persistent identifier derived from your machine's hardware:

- **macOS**: SHA-512 hash of system serial number
- **Linux/Windows**: SHA-512 hash of primary MAC address
- **Fallback**: Generated UUID (only if hardware methods unavailable)

The UUID is computed on-demand and changes only if the hashing algorithm is updated across major versions.

### Viewing Your Machine UUID

```bash
bsec show machine
```

Output:
```
                 Machine Identity                  
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Property     ┃ Value                            ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Machine UUID │ b0be8437a7ed7184dca0aedab66e0896 │
│ UUID Source  │ macOS System Serial (SHA-512)    │
│ Hostname     │ Jakes-MacBook-Air-5.local        │
│ Node Name    │ Jakes-MacBook-Air-5.local        │
│ Architecture │ arm64                            │
└──────────────┴──────────────────────────────────┘

The machine UUID is stable across hostname changes and reboots.
It's derived from hardware identifiers when possible.
```

## Storage Locations

### 1. 1Password Key Entry

**Item**: "Bastion Cache Key" (Secure Note in Private vault)

**Fields**:
- `encryption_key` - The Fernet encryption key (updated on rotation)
- `machine_uuid` - UUID of machine that created/rotated the key
- `created_on_machine` - UUID of machine that initially created the key
- `rotated_at` - Timestamp of last rotation
- `rotated_on_machine` - UUID of machine that last rotated the key
- `last_machine_hostname` - Hostname of last rotation machine

### 2. Cache Metadata

**File**: `~/.bsec/cache/db.enc` (encrypted)

**Metadata Fields**:
- `machine_uuid` - Current machine's UUID (set on every sync)
- `machine_hostname` - Current machine's hostname
- `machine_node` - Current machine's node name

### 3. File Fallback (Rarely Used)

**File**: `~/.bsec/machine-uuid` (only created if hardware methods fail)

- Plain text file containing the UUID
- Survives across reboots and cache deletions
- Set with restrictive permissions (0o600)

## Synchronization Workflow

### First Machine (Key Creation)

```bash
# Machine A
bsec 1p sync vault
# Creates:
# - Fernet encryption key in 1P
# - Machine UUID A in 1P key entry
# - Encrypted cache with UUID A in metadata
```

### Second Machine (Safe Multi-Machine Access)

```bash
# Machine B
bsec 1p sync vault
# 1. Validates: current UUID B vs 1P key UUID A
# 2. Detects mismatch
# 3. Auto-recovery triggered:
#    - Backs up old cache to ~/.bsec/backups/db.enc.bad-<timestamp>
#    - Initializes fresh database
#    - Syncs all 443 accounts from 1P
#    - Updates 1P key entry machine_uuid to B
```

### Same Machine After Key Rotation

```bash
# Machine A (after 90-day encryption key rotation)
bsec 1p sync vault
# 1. Validates: UUID A (cached) vs UUID A (1P) ✓ Match
# 2. Decrypts cache with old key
# 3. Re-encrypts with new key
# 4. Updates 1P entry: machine_uuid still A
# 5. Backup created: ~/.bsec/backups/db.enc.<timestamp>
```

## Auto-Recovery Behavior

### Trigger: UUID Mismatch

When `bsec 1p sync vault` runs:

1. **Validation Phase**: Read machine_uuid from 1Password key entry
2. **Current UUID**: Compute UUID for current machine
3. **Comparison**: If UUIDs don't match:
   - Backup old cache: `~/.bsec/backups/db.enc.bad-YYYYMMDDTHHMMSSZ`
   - Initialize fresh database
   - Sync all items from 1Password
   - Update 1P key entry with new UUID

### Data Preservation

- Old cache backed up with timestamp
- No data loss (can restore from 1P)
- New cache built from live 1P vault data

### Error Message

```
EncryptionError: Machine UUID mismatch: cache key was created on 
a different machine.
Key machine UUID: 8227af8750a7a9b8ac2261c8234e4f0e
Current machine UUID: b0be8437a7ed7184dca0aedab66e0896
Auto-recovery: backing up cache and creating fresh database.
```

## Best Practices

### Multi-Machine Setup

1. **Primary Machine**: Run `bsec 1p sync vault` first to initialize
2. **Secondary Machines**: Run sync; auto-recovery will set up cache
3. **Verify**: Run `bsec show machine` on each machine to see UUID

### Moving to New Machine

```bash
# Old machine: backup optional (1P has everything)
# New machine:
bsec 1p sync vault
# Auto-recovery sets up fresh cache with new UUID
# All accounts sync from 1P automatically
```

### Hostname Changes

Machine UUID remains stable even after hostname change:

```bash
# Old hostname
bsec show machine
# → UUID: b0be8437...

# Change hostname
sudo scutil --set ComputerName "new-name"

# UUID unchanged (derived from serial/MAC, not hostname)
bsec show machine
# → UUID: b0be8437... (same)
```

### Backup and Recovery

```bash
# View backups
ls -la ~/.bsec/backups/

# Restore from backup (if needed)
# Manual: copy db.enc.bad-<timestamp> to db.enc
# Then run: bsec 1p sync vault
```

## Key Rotation

Encryption key rotates automatically every 90 days (configurable):

```bash
# During sync, if key is 90+ days old:
bsec 1p sync vault
# → Rolling encryption key...
# → New key generated and stored in 1P
# → Cache re-encrypted
# → Old backup created: ~/.bsec/backups/db.enc.<timestamp>
```

Machine UUID remains unchanged during key rotation (same machine).

## Troubleshooting

### "Machine UUID mismatch" Error

**Cause**: Cache created on different machine or algorithm changed

**Solution**: Run `bsec 1p sync vault` to trigger auto-recovery

**What happens**:
- Old cache backed up
- Fresh database created
- Accounts re-synced from 1P
- Machine UUID updated in 1P

### Suspicious Backup Files

```bash
# Check backup integrity
ls -la ~/.bsec/backups/db.enc.bad-*

# If you want to restore a backup:
# 1. Rename: mv db.enc.bad-<ts> db.enc
# 2. Sync: bsec 1p sync vault
```

### Different UUIDs on Same Machine After Update

**Cause**: Hashing algorithm changed (e.g., SHA-256 to SHA-512)

**Solution**: 
```bash
bsec 1p sync vault
# Auto-recovery handles it:
# - Backs up old cache
# - Creates fresh from 1P
# - Updates 1P with new UUID
```

## Security Implications

### Prevents

- ✅ Cache decryption on wrong machine
- ✅ Key misuse across machines
- ✅ Accidental data corruption from mismatched keys

### Preserves

- ✅ All account data (stored in 1Password)
- ✅ Rotation history (stored in 1Password)
- ✅ Full recovery via 1Password sync

### Requires

- 🔐 Access to 1Password vault (for encryption key)
- 🔐 1Password CLI authentication
- 🔐 Machine hardware identifier stability

## Implementation Details

### UUID Generation Algorithm (v0.3.1)

```python
# bastion-core/src/bastion_core/platform.py

def get_machine_uuid() -> str:
    # Priority order:
    if is_macos():
        # Try system serial number
        result = subprocess.run(["system_profiler", "SPHardwareDataType"])
        serial = extract_serial(result.stdout)
        return hashlib.sha512(serial.encode()).hexdigest()[:32]
    
    # Fallback: MAC address
    mac = uuid.getnode()
    return hashlib.sha512(str(mac).encode()).hexdigest()[:32]
    
    # Last resort: generated UUID
    return uuid.uuid4().hex[:32]
```

### Validation Flow

```python
# packages/bastion/src/bastion/db.py

def load(self) -> Database:
    try:
        # 1. Validate machine UUID matches 1P entry
        self._validate_machine_uuid()
        
        # 2. Decrypt cache with key from 1P
        encrypted_data = self.cache_path.read_bytes()
        decrypted = self._decrypt(encrypted_data)
        
        # 3. Parse and return
        return Database.model_validate(json.loads(decrypted))
        
    except EncryptionError:
        # Auto-recovery:
        # - Backup bad cache
        # - Initialize fresh database
        return self._initialize_new()
```

## References

- [Crypto Standards](/reference/CRYPTO-FUNCTION-MATRIX.html) — SHA-512 algorithm details
- [Getting Started](/getting-started/GETTING-STARTED.html) — First-time setup
- [Cache Configuration](../../packages/bastion/src/bastion/config.py) — Paths and settings