SaveCredentials saves API credentials to file using atomic write to prevent TOCTOU race
(apiID, apiKey string)
| 283 | |
| 284 | // SaveCredentials saves API credentials to file using atomic write to prevent TOCTOU race |
| 285 | func (m *Manager) SaveCredentials(apiID, apiKey string) error { |
| 286 | if err := m.setupDirectories(); err != nil { |
| 287 | return err |
| 288 | } |
| 289 | |
| 290 | m.credentials = &models.Credentials{ |
| 291 | APIID: apiID, |
| 292 | APIKey: apiKey, |
| 293 | } |
| 294 | |
| 295 | // Generate YAML content manually to avoid viper's default file creation |
| 296 | content := fmt.Sprintf("api_id: %s\napi_key: %s\n", apiID, apiKey) |
| 297 | |
| 298 | // Use atomic write pattern to prevent TOCTOU race condition: |
| 299 | // 1. Write to temp file with secure permissions from the start |
| 300 | // 2. Atomically rename to target file |
| 301 | dir := filepath.Dir(m.config.CredentialsFile) |
| 302 | |
| 303 | // Create temp file in same directory (required for atomic rename) |
| 304 | // Use O_CREATE|O_EXCL to prevent race on temp file creation |
| 305 | // File is created with 0600 permissions from the start |
| 306 | tmpFile, err := os.CreateTemp(dir, ".credentials-*.tmp") |
| 307 | if err != nil { |
| 308 | return fmt.Errorf("error creating temp credentials file: %w", err) |
| 309 | } |
| 310 | tmpPath := tmpFile.Name() |
| 311 | |
| 312 | // Clean up temp file on any error |
| 313 | defer func() { |
| 314 | if tmpFile != nil { |
| 315 | if err := tmpFile.Close(); err != nil { |
| 316 | // Use a logger if available, otherwise ignore in defer |
| 317 | _ = err |
| 318 | } |
| 319 | } |
| 320 | // Remove temp file if it still exists (rename failed or error occurred) |
| 321 | if err := os.Remove(tmpPath); err != nil && !os.IsNotExist(err) { |
| 322 | // Ignore "file not found" errors in cleanup |
| 323 | _ = err |
| 324 | } |
| 325 | }() |
| 326 | |
| 327 | // Set secure permissions on temp file before writing content |
| 328 | if err := tmpFile.Chmod(0600); err != nil { |
| 329 | return fmt.Errorf("error setting temp file permissions: %w", err) |
| 330 | } |
| 331 | |
| 332 | // Write credentials to temp file |
| 333 | if _, err := tmpFile.WriteString(content); err != nil { |
| 334 | return fmt.Errorf("error writing credentials to temp file: %w", err) |
| 335 | } |
| 336 | |
| 337 | // Ensure data is flushed to disk before rename |
| 338 | if err := tmpFile.Sync(); err != nil { |
| 339 | return fmt.Errorf("error syncing temp file: %w", err) |
| 340 | } |
| 341 | |
| 342 | // Close the file before rename (required on some systems) |
no test coverage detected