From af8e64f94e87cb5290483cc7068a6271a46768f0 Mon Sep 17 00:00:00 2001 From: baeyc0510 Date: Thu, 11 Dec 2025 20:14:41 +0900 Subject: [PATCH] fix: prevent 'admin with no permissions' on fresh install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes permission issues when user-policy.json creation fails or is missing. Changes: 1. init.go: Make createDefaultPolicy() failure fatal - Previously: warning only, continued execution - Now: exits with detailed error message - Provides actionable guidance (antivirus, permissions, disk space) 2. manager.go: Add safe RBAC fallback in LoadPolicy() - Previously: returned RBAC=nil when file missing - Now: returns minimal admin role with full permissions - Prevents "admin with no permissions" scenario in dashboard 3. manager.go: Enhanced error messages for SavePolicy() - Added specific causes for write failures - Windows-specific guidance (antivirus, OneDrive, UAC) Root Cause: - On Windows, file write can fail due to: * Antivirus real-time protection scanning binary * OneDrive/Dropbox sync conflicts * Controlled Folder Access (ransomware protection) * Transient I/O errors - Silent failure left .env with CURRENT_ROLE=admin but no policy file - Dashboard loaded RBAC=nil → permission check failed Impact: - Prevents confusing UX where admin role has no edit buttons - Provides clear error messages to help users resolve issues - Fails fast instead of creating inconsistent state --- internal/cmd/init.go | 11 +++++++++-- internal/policy/manager.go | 26 ++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/internal/cmd/init.go b/internal/cmd/init.go index fee62d8..33ad528 100644 --- a/internal/cmd/init.go +++ b/internal/cmd/init.go @@ -101,8 +101,15 @@ func runInit(cmd *cobra.Command, args []string) { // Create default policy file with RBAC roles (only if not exists or --force) policyCreated, err := createDefaultPolicy() if err != nil { - printWarn(fmt.Sprintf("Failed to create policy file: %v", err)) - fmt.Println(indent("You can manually create it later using the dashboard")) + printError(fmt.Sprintf("Failed to create policy file: %v", err)) + fmt.Println() + printError("This is a critical error. Initialization cannot continue.") + fmt.Println(indent("Possible causes:")) + fmt.Println(indent(" - Disk write permissions (try running as administrator on Windows)")) + fmt.Println(indent(" - Antivirus blocking file creation (add sym to exclusions)")) + fmt.Println(indent(" - OneDrive/Dropbox sync conflict (pause sync temporarily)")) + fmt.Println(indent(" - Disk full or read-only filesystem")) + os.Exit(1) } else if policyCreated { printOK("user-policy.json created with default RBAC roles") } else { diff --git a/internal/policy/manager.go b/internal/policy/manager.go index 4d27911..4951ce2 100644 --- a/internal/policy/manager.go +++ b/internal/policy/manager.go @@ -37,10 +37,19 @@ func LoadPolicy(customPath string) (*schema.UserPolicy, error) { data, err := os.ReadFile(policyPath) if err != nil { if os.IsNotExist(err) { - // Return empty policy if file doesn't exist + // Return safe fallback with minimal RBAC to prevent "admin with no permissions" return &schema.UserPolicy{ Version: "1.0.0", - Rules: []schema.UserRule{}, + RBAC: &schema.UserRBAC{ + Roles: map[string]schema.UserRole{ + "admin": { + AllowWrite: []string{"**/*"}, + CanEditPolicy: true, + CanEditRoles: true, + }, + }, + }, + Rules: []schema.UserRule{}, }, nil } return nil, err @@ -64,7 +73,7 @@ func SavePolicy(policy *schema.UserPolicy, customPath string) error { // Ensure directory exists dir := filepath.Dir(policyPath) if err := os.MkdirAll(dir, 0755); err != nil { - return err + return fmt.Errorf("failed to create directory %s: %w\nPossible causes: insufficient permissions, disk full, or antivirus blocking", dir, err) } // Validate policy before saving @@ -77,7 +86,16 @@ func SavePolicy(policy *schema.UserPolicy, customPath string) error { return err } - return os.WriteFile(policyPath, data, 0644) + if err := os.WriteFile(policyPath, data, 0644); err != nil { + return fmt.Errorf("failed to write policy file to %s: %w\n"+ + "Possible causes:\n"+ + " - Insufficient permissions (try running as administrator on Windows)\n"+ + " - Antivirus blocking file creation (add symphony to exclusions)\n"+ + " - OneDrive/Dropbox sync conflict (pause sync temporarily)\n"+ + " - Disk full or read-only filesystem", policyPath, err) + } + + return nil } // ValidatePolicy validates the policy structure