-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Sync enhancements and audit improvements (ARI-50, ARI-53, ARI-54, ARI-55) #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ARI-50: Add source machine tracking to audit logs - Add MachineID field to AuditLogEntry struct - Update Sign/Verify to include MachineID in HMAC signature - Add GetMachineID() helper function (returns hostname) - Update LogAudit() to populate MachineID automatically ARI-53: Add sync health check to doctor command - Add SyncCheckDetails type for structured sync status - Create SyncChecker to verify rclone availability and config - Register sync check in doctor command health checks - Check: sync enabled, remote configured, rclone installed/version 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
ARI-53: Add cloud sync setup prompts - Add --no-sync flag to skip sync prompts - Add offerSyncSetup() for new vault sync configuration - Check rclone installation and remote connectivity - Save sync config to config.yaml ARI-54: Add connect-to-existing-vault flow - Ask "new or connect" as first question in init - Add runConnectFlow() to download existing vault from remote - Verify master password works after download - Auto-configure sync for connected vaults 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add readLineInput() helper for test-compatible stdin reading - Update all init stdin helpers with sync prompts (ARI-53/54) - Fix init.go to use readLineInput() instead of bufio.NewReader - Update hardcoded stdin strings in vault_ops_test.go - Update recovery_test.go with new prompt order 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The keychain status test now correctly matches all Linux backend formats: - "Linux Secret Service" (original) - "Secret Service API" (actual dbus output) - "gnome-keyring" / "kwallet" (specific backends) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Increase timing tolerance from 6s to 7s (5s + 2s for system scheduling) - Skip test gracefully when clipboard is modified by external process - Use blocking channel receive instead of polling loop The clipboard is a shared system resource that can be modified by parallel test packages, causing flaky failures. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add `pass-cli sync enable` command allowing users with existing local vaults to enable cloud sync. This complements the init workflow which only supports new vaults or connecting to existing remote vaults. Features: - Validates vault exists before proceeding - Checks if sync is already enabled (prevents double-enable) - Validates rclone is installed - Prompts for rclone remote path with examples - Validates remote connectivity before saving config - Detects if remote already has files (--force to overwrite) - Performs initial push to remote after setup New files: - cmd/sync.go: Parent command for sync subcommands - cmd/sync_enable.go: Enable sync on existing vault - test/integration/sync_enable_test.go: Integration tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Update documentation to reflect new sync capabilities: - docs/02-guides/sync-guide.md: - Add "Enabling Sync" section with two options - Document `sync enable` command for existing vaults - Update "Connecting to Existing Vault" with new init flow - docs/03-reference/command-reference.md: - Add `sync` parent command section - Add `sync enable` subcommand with flags and examples - Update `init` command with --no-sync flag - Update `doctor` to mention sync health check - docs/05-operations/health-checks.md: - Add "Sync Check" section documenting sync health verification - Include troubleshooting for rclone/remote issues - docs/01-getting-started/quick-start.md: - Add "First Choice: New or Existing Vault" section - Document new interactive init prompts - Add --no-sync flag to Advanced Options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Update init description to mention new/connect choice - Add `sync enable` command to Core Commands section - Note that doctor includes sync status 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| cmd/sync_enable.go | Adds sync enable command with rclone validation and config management |
| cmd/init.go | Major expansion with new/connect flow and sync setup; has config management concerns |
| internal/security/audit.go | Added MachineID field for cross-device tracking - clean implementation |
| internal/health/sync.go | New sync health checker for doctor command - well-structured |
| cmd/helpers.go | Added sync helper functions (maybeSyncPull/Push) with session state tracking |
Sequence Diagram
sequenceDiagram
participant User
participant CLI as pass-cli
participant Config
participant Vault
participant Rclone
participant Remote as Cloud Storage
Note over User,Remote: ARI-54: Connect to Existing Vault Flow
User->>CLI: pass-cli init
CLI->>User: New vault or connect? (1/2)
User->>CLI: 2 (connect)
CLI->>Rclone: Check rclone installed
CLI->>User: Enter remote path
User->>CLI: gdrive:.pass-cli
CLI->>Rclone: rclone sync remote → local
Rclone->>Remote: Download vault files
Remote-->>Rclone: vault.enc, metadata
Rclone-->>CLI: Vault downloaded
CLI->>User: Enter master password
User->>CLI: password
CLI->>Vault: Unlock(password)
Vault-->>CLI: Success
CLI->>Config: saveSyncConfig(remote)
Config-->>CLI: Config saved
CLI->>User: ✅ Connected!
Note over User,Remote: ARI-55: Enable Sync on Existing Vault
User->>CLI: pass-cli sync enable
CLI->>Vault: Check vault exists
Vault-->>CLI: Exists
CLI->>Config: Check sync already enabled
Config-->>CLI: Not enabled
CLI->>Rclone: Check rclone installed
CLI->>User: Enter remote path
User->>CLI: gdrive:.pass-cli
CLI->>Rclone: rclone lsd remote (validate)
Rclone->>Remote: Check connectivity
Remote-->>Rclone: Connected
CLI->>Rclone: rclone ls remote (check files)
Rclone->>Remote: List files
Remote-->>Rclone: Empty or files exist
CLI->>Config: saveSyncConfig(remote)
Config-->>CLI: Config saved
CLI->>Rclone: rclone sync local → remote
Rclone->>Remote: Push vault files
Remote-->>Rclone: Success
CLI->>User: ✅ Sync enabled!
Note over User,Remote: ARI-53: Doctor Command Sync Check
User->>CLI: pass-cli doctor
CLI->>Config: Load sync config
Config-->>CLI: enabled, remote
CLI->>Rclone: exec.LookPath("rclone")
Rclone-->>CLI: rclone path or not found
CLI->>Rclone: rclone version
Rclone-->>CLI: version string
CLI->>User: ✅ Sync configured and ready
Note over User,Remote: ARI-50: Audit Log with MachineID
User->>CLI: pass-cli get github
CLI->>CLI: GetMachineID() → hostname
CLI->>Vault: Unlock and access credential
Vault->>Vault: Log audit entry with MachineID
Vault-->>CLI: Credential retrieved
CLI->>User: Display credential
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional Comments (1)
-
cmd/helpers.go, line 382-414 (link)style:
maybeSyncPullonly syncs once per session via globalsyncedThisSessionflag, but this prevents pulling updates if user runs multiple commands in separate invocations. The session state doesn't persist across CLI calls, so this only prevents multiple pulls within a single command execution (which wouldn't happen anyway)
27 files reviewed, 3 comments
| func saveSyncConfig(remote string) error { | ||
| configPath, err := config.GetConfigPath() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Read existing config or create new | ||
| content, err := os.ReadFile(configPath) | ||
| if err != nil && !os.IsNotExist(err) { | ||
| return err | ||
| } | ||
|
|
||
| // Simple append/update for sync config | ||
| syncConfig := fmt.Sprintf("\nsync:\n enabled: true\n remote: \"%s\"\n", remote) | ||
|
|
||
| if len(content) == 0 { | ||
| content = []byte(syncConfig) | ||
| } else { | ||
| // Check if sync section already exists | ||
| if strings.Contains(string(content), "sync:") { | ||
| // Replace existing sync section (simplified - could be more robust) | ||
| // For now, just warn and skip | ||
| return nil | ||
| } | ||
| content = append(content, []byte(syncConfig)...) | ||
| } | ||
|
|
||
| return os.WriteFile(configPath, content, 0600) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: saveSyncConfig appends config without parsing YAML structure - if sync section exists, silently returns nil and doesn't update remote. This means if user has sync: anywhere in config (even with different remote), the new remote won't be saved.
| func saveSyncConfig(remote string) error { | |
| configPath, err := config.GetConfigPath() | |
| if err != nil { | |
| return err | |
| } | |
| // Read existing config or create new | |
| content, err := os.ReadFile(configPath) | |
| if err != nil && !os.IsNotExist(err) { | |
| return err | |
| } | |
| // Simple append/update for sync config | |
| syncConfig := fmt.Sprintf("\nsync:\n enabled: true\n remote: \"%s\"\n", remote) | |
| if len(content) == 0 { | |
| content = []byte(syncConfig) | |
| } else { | |
| // Check if sync section already exists | |
| if strings.Contains(string(content), "sync:") { | |
| // Replace existing sync section (simplified - could be more robust) | |
| // For now, just warn and skip | |
| return nil | |
| } | |
| content = append(content, []byte(syncConfig)...) | |
| } | |
| return os.WriteFile(configPath, content, 0600) | |
| } | |
| func saveSyncConfig(remote string) error { | |
| configPath, err := config.GetConfigPath() | |
| if err != nil { | |
| return err | |
| } | |
| // Load existing config | |
| cfg, err := config.Load() | |
| if err != nil && !os.IsNotExist(err) { | |
| return err | |
| } | |
| if cfg == nil { | |
| cfg = &config.Config{} | |
| } | |
| // Update sync config | |
| cfg.Sync.Enabled = true | |
| cfg.Sync.Remote = remote | |
| // Save config using proper YAML marshaling | |
| return config.Save(cfg) | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: cmd/init.go
Line: 440:468
Comment:
**logic:** `saveSyncConfig` appends config without parsing YAML structure - if sync section exists, silently returns nil and doesn't update remote. This means if user has `sync:` anywhere in config (even with different remote), the new remote won't be saved.
```suggestion
func saveSyncConfig(remote string) error {
configPath, err := config.GetConfigPath()
if err != nil {
return err
}
// Load existing config
cfg, err := config.Load()
if err != nil && !os.IsNotExist(err) {
return err
}
if cfg == nil {
cfg = &config.Config{}
}
// Update sync config
cfg.Sync.Enabled = true
cfg.Sync.Remote = remote
// Save config using proper YAML marshaling
return config.Save(cfg)
}
```
How can I resolve this? If you propose a fix, please make it concise.| if err := saveSyncConfig(remote); err != nil { | ||
| return fmt.Errorf("failed to save sync config: %w", err) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Same config save issue as in cmd/init.go:440 - uses string concatenation instead of proper YAML marshaling
Prompt To Fix With AI
This is a comment left during a code review.
Path: cmd/sync_enable.go
Line: 121:123
Comment:
**logic:** Same config save issue as in `cmd/init.go:440` - uses string concatenation instead of proper YAML marshaling
How can I resolve this? If you propose a fix, please make it concise.BuildInitStdin with SkipVerify=false (default) generates "y" for mnemonic verification but doesn't provide the 6 words needed, causing the init to fail on CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Addresses Greptile review feedback - saveSyncConfig now properly parses existing config and updates the sync section instead of using string concatenation that silently failed if sync: section already existed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
configPath comes from config.GetConfigPath() (trusted internal function), not from user input. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
This PR adds comprehensive cloud sync enhancements and audit log improvements:
doctorcommand + sync setup during initsync enablecommand for existing vaultsChanges
New Features
pass-cli sync enable- Enable cloud sync on an existing vaultpass-cli initnow offers choice: "Create new vault" vs "Connect to existing synced vault"pass-cli doctorincludes sync health status (rclone installed, remote configured)MachineIDfor cross-device trackingNew Files
cmd/sync.go- Parent command for sync subcommandscmd/sync_enable.go- Enable sync on existing vaultinternal/health/sync.go- Sync health checker for doctor commandtest/integration/sync_enable_test.go- Integration testsBug Fixes
Documentation
sync enablecommandTest plan
sync enableintegration tests passRelated Issues
🤖 Generated with Claude Code