@@ -242,17 +242,42 @@ type Credential interface {
242242
243243** Implementation Details:**
244244- JWT tokens are parsed ONLY to extract provider information (for display purposes)
245- - No client-side expiration checking - the API server is the source of truth for token validity
246- - When API returns 401 Unauthorized, users receive helpful error message with guidance
245+ - ** Automatic token refresh** : When API returns 401 Unauthorized, the server automatically refreshes the token
246+ - ** File watching** : The server watches the credential file and reloads tokens when Terramate CLI updates them
247+ - ** Thread-safe** : All credential operations use mutex protection for concurrent access
248+ - ** Atomic file updates** : Credential file updates are atomic to prevent corruption
247249- Uses ` Authorization: Bearer <token> ` header
248- - No automatic refresh in MCP server (users must re-run ` terramate cloud login ` )
250+ - ** Zero maintenance** : No manual token refresh or server restarts needed
251+
252+ ** Automatic Token Refresh:**
253+ The MCP server implements a hybrid approach for seamless token management:
254+ 1 . ** Reactive Refresh** : When API returns 401, server refreshes token and retries request
255+ 2 . ** File Watching** : Server watches ` ~/.terramate.d/credentials.tmrc.json ` for external updates
256+ 3 . ** Shared Credentials** : Both MCP server and Terramate CLI safely share the same credential file
257+ 4 . ** Atomic Updates** : File updates use atomic operations to prevent race conditions
258+
259+ ** How it Works:**
260+ ```
261+ ┌─────────────────┐ ┌──────────────────┐
262+ │ Terramate CLI │◄───────►│ Credential File │
263+ │ (Token Manager)│ writes │ (Shared State) │
264+ └─────────────────┘ └──────────────────┘
265+ ▲
266+ │ watches
267+ │ & reads
268+ ▼
269+ ┌──────────────────┐
270+ │ MCP Server │
271+ │ (Auto-Refresh) │
272+ └──────────────────┘
273+ ```
249274
250275** Security Note:**
251276The client does NOT validate JWT expiration locally. This is intentional and follows security best practices:
252277- Client-side parsing uses ` ParseUnverified() ` which doesn't verify signatures
253278- Making security decisions based on unverified data would be unsafe
254279- The API server is the authoritative source for token validation
255- - 401 errors from API provide clear guidance to users to refresh credentials
280+ - 401 errors trigger automatic token refresh - transparent to users
256281
257282### API Key Authentication (issuing an organization API key requires admin privileges)
258283
@@ -344,4 +369,139 @@ func (s *Service) SomeMethod(ctx context.Context, ...) error {
344369- The SDK will refuse to load credential files with insecure permissions
345370- Never commit credential files to git
346371- ` .terramate.d/ ` should be in ` .gitignore `
347- - MCP server reads credentials on startup only (not monitored for changes)
372+ - MCP server watches the credential file for changes and automatically reloads tokens
373+
374+ ## Security Best Practices
375+
376+ ### 🔒 Preventing Token Leakage
377+
378+ ** CRITICAL: Never expose tokens, API keys, or credentials in:**
379+ - Error messages
380+ - Log messages
381+ - Debug output
382+ - HTTP response bodies in error messages
383+ - Stack traces
384+ - Test output (unless sanitized)
385+
386+ ** When handling errors:**
387+ ``` go
388+ // ❌ BAD: Leaks token in error message
389+ return fmt.Errorf (" refresh failed: %s " , string (responseBody))
390+
391+ // ✅ GOOD: Parse JSON safely, extract only safe fields
392+ var errResp struct {
393+ Error string ` json:"error"`
394+ }
395+ if err := json.Unmarshal (body, &errResp); err == nil {
396+ return fmt.Errorf (" refresh failed: %s " , errResp.Error )
397+ }
398+ return fmt.Errorf (" refresh failed (status %d )" , statusCode)
399+ ```
400+
401+ ** When logging:**
402+ ``` go
403+ // ❌ BAD: Logs token value
404+ log.Printf (" Token: %s " , token)
405+
406+ // ✅ GOOD: Generic log message
407+ log.Printf (" JWT token refreshed successfully" )
408+
409+ // ✅ GOOD: Log error without token
410+ log.Printf (" Warning: failed to reload credential: %v " , err)
411+ ```
412+
413+ ** When handling HTTP responses:**
414+ ``` go
415+ // ❌ BAD: Includes raw body in error (may contain tokens)
416+ apiErr := &APIError{Message: string (body)}
417+
418+ // ✅ GOOD: Parse JSON safely, extract only error fields
419+ apiErr := &APIError{Message: " API request failed" }
420+ if isJSONContentType (resp.Header .Get (" Content-Type" )) {
421+ var errResp ErrorResponse
422+ if err := json.Unmarshal (body, &errResp); err == nil {
423+ apiErr.Message = errResp.Error // Only safe parsed field
424+ }
425+ }
426+ ```
427+
428+ ### Security Checklist for New Code
429+
430+ When adding or modifying code that handles credentials:
431+
432+ - [ ] ** Error Messages** : Never include tokens, API keys, or raw HTTP response bodies
433+ - [ ] ** Logging** : Use generic messages, never log credential values
434+ - [ ] ** JSON Parsing** : Parse error responses safely, extract only known safe fields
435+ - [ ] ** HTTP Bodies** : Never convert response bodies to strings for error messages without parsing
436+ - [ ] ** Test Output** : In tests, only log token prefixes (e.g., ` token[:20]+"..." ` ) if needed
437+ - [ ] ** File Permissions** : Always validate credential file permissions (` 0600 ` )
438+ - [ ] ** Thread Safety** : Use mutexes for concurrent credential access
439+ - [ ] ** Input Validation** : Validate all inputs before processing
440+ - [ ] ** HTTPS Only** : Never use HTTP for credential transmission
441+ - [ ] ** Context Timeouts** : Use context timeouts for all network operations
442+
443+ ### Common Security Anti-Patterns to Avoid
444+
445+ ** 1. Token Leakage in Errors:**
446+ ``` go
447+ // ❌ BAD
448+ return fmt.Errorf (" failed: %s " , string (httpResponseBody))
449+
450+ // ✅ GOOD
451+ return fmt.Errorf (" failed: %s " , parseSafeError (httpResponseBody))
452+ ```
453+
454+ ** 2. Logging Credentials:**
455+ ``` go
456+ // ❌ BAD
457+ log.Printf (" Using token: %s " , token)
458+
459+ // ✅ GOOD
460+ log.Printf (" Using JWT authentication" )
461+ ```
462+
463+ ** 3. Including Raw Bodies:**
464+ ``` go
465+ // ❌ BAD
466+ err := fmt.Errorf (" API error: %s " , string (body))
467+
468+ // ✅ GOOD
469+ err := parseAPIError (resp, body) // Safely parses JSON
470+ ```
471+
472+ ** 4. Debug Output:**
473+ ``` go
474+ // ❌ BAD
475+ fmt.Printf (" Token: %v \n " , credential)
476+
477+ // ✅ GOOD
478+ fmt.Printf (" Credential type: %s \n " , credential.Name ())
479+ ```
480+
481+ ### Security Review Process
482+
483+ Before committing code that handles credentials:
484+
485+ 1 . ** Search for token leakage:**
486+ ``` bash
487+ grep -r " fmt.*token\|log.*token\|string(body)" --include=" *.go"
488+ ```
489+
490+ 2 . ** Verify error handling:**
491+ - Check all ` fmt.Errorf() ` calls with ` %s ` or ` %v ` formatting
492+ - Ensure HTTP response bodies are parsed, not converted to strings
493+ - Verify error messages don't include credential values
494+
495+ 3 . ** Check logging:**
496+ - Search for ` log.Printf ` or ` log.Println ` with credential variables
497+ - Ensure all log messages are generic
498+
499+ 4 . ** Test security:**
500+ - Run tests and verify no tokens appear in output
501+ - Check error messages don't expose sensitive data
502+ - Verify file permissions are enforced
503+
504+ 5 . ** Review HTTP handling:**
505+ - Ensure all API calls use HTTPS
506+ - Verify response bodies are parsed safely
507+ - Check error handling doesn't leak response bodies
0 commit comments