Files
ruflo/docs/github-cli-safety-wrapper.md
T
rUv a99b7e4cd8 🚀 Fix headless mode for swarm command - Enable Claude CLI in non-interactive environments
- Move headless configuration to beginning of swarmCommand function
- Fix issue where headless mode was falling back to basic executor
- Ensure stream-json format is used for Claude compatibility
- Clean up debug console.log statements
- Support CI/CD and Docker environments with proper non-interactive flags
2025-07-29 20:29:02 +00:00

16 KiB

GitHub CLI Safety Wrapper

A comprehensive, production-ready wrapper around GitHub CLI commands that provides robust security, error handling, and reliability features.

Features

  • 🛡️ Injection Attack Prevention: Validates and sanitizes all inputs to prevent command injection
  • ⏱️ Timeout Handling: Configurable timeouts with graceful process cleanup
  • 🔄 Retry Logic: Exponential backoff retry mechanism for transient failures
  • 📊 Rate Limiting: Built-in rate limiting to prevent API abuse
  • 🗂️ Secure Temp Files: Safe temporary file handling with proper permissions
  • 📈 Monitoring: Comprehensive statistics and active process tracking
  • 🚨 Error Recovery: Robust error handling with custom error types
  • 🧪 Validation: Input validation with size limits and pattern detection

Installation

The wrapper is part of the Claude Flow project and can be imported directly:

import { GitHubCliSafe, githubCli } from './src/utils/github-cli-safety-wrapper.js';

Quick Start

Using the Singleton Instance

import { githubCli } from './src/utils/github-cli-safety-wrapper.js';

// Create an issue
const result = await githubCli.createIssue({
  title: 'Bug Report: Login fails with special characters',
  body: 'Steps to reproduce:\n1. Enter `username@domain.com`\n2. Use password with $(special) characters',
  labels: ['bug', 'high-priority'],
  assignees: ['maintainer1', 'maintainer2']
});

console.log('Issue created:', result.stdout);

Creating a Custom Instance

import { GitHubCliSafe } from './src/utils/github-cli-safety-wrapper.js';

const ghSafe = new GitHubCliSafe({
  timeout: 60000,           // 60 seconds
  maxRetries: 5,            // Retry failed operations 5 times
  enableRateLimit: true,    // Enable rate limiting
  enableLogging: true       // Enable debug logging
});

API Reference

GitHubCliSafe Class

Constructor Options

const options = {
  timeout: 30000,              // Default timeout in milliseconds
  maxRetries: 3,               // Maximum retry attempts
  retryDelay: 1000,            // Base retry delay in milliseconds
  enableRateLimit: true,       // Enable rate limiting
  enableLogging: true,         // Enable logging
  tempDir: '/tmp',             // Temporary file directory
  maxRequestsPerWindow: 50,    // Rate limit: requests per window
  rateLimitWindow: 60000       // Rate limit: window size in milliseconds
};

Core Methods

execute(command, options)

Execute a GitHub CLI command with full safety features.

const result = await ghSafe.execute('issue create', {
  title: 'My Issue',
  body: 'Issue description with `code` and special chars',
  labels: ['bug', 'needs-triage'],
  assignees: ['reviewer1'],
  flags: {
    milestone: 'v2.0.0',
    project: 'Main Project'
  }
});

Parameters:

  • command (string): The GitHub CLI command (e.g., 'issue create', 'pr comment')
  • options (object): Configuration options
    • title (string): Title for issues/PRs
    • body (string): Body content (uses secure temp files)
    • labels (array): Array of label names
    • assignees (array): Array of assignee usernames
    • flags (object): Additional CLI flags
    • timeout (number): Override default timeout
    • env (object): Environment variables
    • cwd (string): Working directory

Returns: Promise resolving to:

{
  stdout: string,     // Command output
  stderr: string,     // Error output
  code: number,       // Exit code
  duration: number,   // Execution time in ms
  command: string     // Full command executed
}

High-Level Methods

createIssue(params)

Create a GitHub issue with safe handling.

const result = await ghSafe.createIssue({
  title: 'Feature Request: Add dark mode',
  body: `
## Description
Add dark mode support to the application.

## Code Example
\`\`\`javascript
const theme = getTheme() === 'dark' ? darkTheme : lightTheme;
\`\`\`

## Implementation Notes
- Use CSS custom properties
- Store preference in localStorage
- Handle system preference changes
`,
  labels: ['enhancement', 'ui'],
  assignees: ['frontend-team']
});
createPR(params)

Create a pull request with safe handling.

const result = await ghSafe.createPR({
  title: 'Fix: Handle special characters in user input',
  body: `
## Changes
- Added input sanitization for user data
- Fixed XSS vulnerability in comment system
- Updated tests for edge cases

## Testing
\`\`\`bash
npm test
npm run security-audit
\`\`\`

## Breaking Changes
None
`,
  base: 'main',
  head: 'fix/input-sanitization',
  draft: false
});
addIssueComment(issueNumber, body, options)

Add a comment to an existing issue.

const result = await ghSafe.addIssueComment(
  123, 
  `
## Update
Fixed the issue in commit abc123.

### Test Results
\`\`\`
✅ All tests passing
✅ Security scan clean
✅ Performance within limits
\`\`\`
`,
  { timeout: 15000 }
);
addPRComment(prNumber, body, options)

Add a comment to a pull request.

const result = await ghSafe.addPRComment(
  456,
  `
## Code Review

The changes look good! A few suggestions:

1. Consider adding error handling for the API call
2. The regex pattern could be more specific: \`/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/\`

**Approved** ✅
`
);
createRelease(params)

Create a GitHub release.

const result = await ghSafe.createRelease({
  tag: 'v2.1.0',
  title: 'Version 2.1.0 - Enhanced Security',
  body: `
## 🚀 Features
- Added GitHub CLI safety wrapper
- Enhanced input validation
- Improved error handling

## 🛡️ Security
- Fixed potential command injection vulnerability
- Added rate limiting protection
- Improved temp file handling

## 📈 Performance
- Reduced memory usage by 15%
- Faster command execution
- Better timeout handling

## 🐛 Bug Fixes
- Fixed issue with special characters in comments
- Resolved timeout problems with large files
- Fixed race condition in process cleanup

## ⚠️ Breaking Changes
None

## 📖 Documentation
- Updated API documentation
- Added security best practices guide
- Improved error handling examples
`,
  prerelease: false,
  draft: false
});

Utility Methods

checkGitHubCli()

Check if GitHub CLI is available and accessible.

const isAvailable = await ghSafe.checkGitHubCli();
if (!isAvailable) {
  console.error('GitHub CLI not found. Please install: https://cli.github.com/');
}
checkAuthentication()

Verify GitHub CLI authentication status.

const isAuthenticated = await ghSafe.checkAuthentication();
if (!isAuthenticated) {
  console.error('Not authenticated. Run: gh auth login');
}
getStats()

Get execution statistics.

const stats = ghSafe.getStats();
console.log('GitHub CLI Usage Stats:', {
  total: stats.totalRequests,
  successful: stats.successfulRequests,
  failed: stats.failedRequests,
  timeouts: stats.timeoutRequests,
  retries: stats.retriedRequests
});
cleanup()

Clean up all active processes (useful for graceful shutdown).

process.on('SIGTERM', async () => {
  console.log('Shutting down...');
  await ghSafe.cleanup();
  process.exit(0);
});

Error Handling

The wrapper provides custom error types for better error handling:

Error Types

GitHubCliError

Base error class for all GitHub CLI operations.

try {
  await ghSafe.execute('invalid command');
} catch (error) {
  if (error instanceof GitHubCliError) {
    console.error('GitHub CLI Error:', {
      message: error.message,
      code: error.code,
      details: error.details,
      timestamp: error.timestamp
    });
  }
}

GitHubCliTimeoutError

Thrown when operations exceed the configured timeout.

try {
  await ghSafe.execute('issue create', { 
    title: 'Test',
    timeout: 1000  // Very short timeout
  });
} catch (error) {
  if (error instanceof GitHubCliTimeoutError) {
    console.error(`Operation timed out after ${error.details.timeout}ms`);
  }
}

GitHubCliValidationError

Thrown when input validation fails.

try {
  await ghSafe.execute('issue create', {
    title: 'Test $(rm -rf /)',  // Dangerous input
    body: 'Description'
  });
} catch (error) {
  if (error instanceof GitHubCliValidationError) {
    console.error('Validation failed:', {
      field: error.details.field,
      value: error.details.value,
      message: error.message
    });
  }
}

GitHubCliRateLimitError

Thrown when rate limits are exceeded.

try {
  // Multiple rapid requests
  for (let i = 0; i < 100; i++) {
    await ghSafe.execute('auth status');
  }
} catch (error) {
  if (error instanceof GitHubCliRateLimitError) {
    console.error('Rate limited. Please wait before making more requests.');
  }
}

Security Features

Input Validation

The wrapper automatically validates and sanitizes all inputs to prevent injection attacks:

// ❌ These inputs are automatically blocked:
const dangerousInputs = [
  '$(rm -rf /)',           // Command substitution
  '`malicious command`',   // Backtick execution
  'test && rm file',       // Command chaining
  'test | sh',             // Pipe to shell
  'eval("code")',          // Eval execution
  'test <(echo hack)',     // Process substitution
];

// ✅ These inputs are allowed:
const safeInputs = [
  'Normal comment text',
  'Code: console.log("hello")',
  'Path: /src/components/Button.jsx',
  'Version: v1.2.3',
  'Special chars: @#$%^&*()_+-=[]{}|'
];

Secure Temp Files

Temporary files are created with restricted permissions (600 - owner read/write only):

// Automatically handled - no manual temp file management needed
await ghSafe.createIssue({
  title: 'Issue with sensitive data',
  body: 'This content is written to a secure temp file with mode 600'
});

Process Isolation

All GitHub CLI commands are executed with:

  • shell: false to prevent shell injection
  • Isolated environment variables
  • Proper signal handling for cleanup

Advanced Usage

Custom Configuration

const enterpriseGhSafe = new GitHubCliSafe({
  timeout: 120000,                    // 2 minutes for slow enterprise
  maxRetries: 5,                      // More retries for reliability
  retryDelay: 2000,                   // Longer delay between retries
  enableRateLimit: true,              // Always enable rate limiting
  maxRequestsPerWindow: 30,           // Conservative rate limit
  rateLimitWindow: 60000,             // 1 minute window
  enableLogging: true,                // Enable detailed logging
  tempDir: '/secure/temp',            // Custom secure temp directory
});

Error Recovery Patterns

async function createIssueWithFallback(issueData) {
  try {
    return await ghSafe.createIssue(issueData);
  } catch (error) {
    if (error instanceof GitHubCliTimeoutError) {
      console.log('Timeout, retrying with longer timeout...');
      return await ghSafe.createIssue({
        ...issueData,
        timeout: 60000
      });
    }
    
    if (error instanceof GitHubCliRateLimitError) {
      console.log('Rate limited, waiting 60 seconds...');
      await new Promise(resolve => setTimeout(resolve, 60000));
      return await ghSafe.createIssue(issueData);
    }
    
    throw error; // Re-throw unhandled errors
  }
}

Monitoring and Logging

// Enable detailed logging
const monitoredGhSafe = new GitHubCliSafe({
  enableLogging: true
});

// Periodic stats logging
setInterval(() => {
  const stats = monitoredGhSafe.getStats();
  const activeProcesses = monitoredGhSafe.getActiveProcessCount();
  
  console.log('GitHub CLI Wrapper Stats:', {
    ...stats,
    activeProcesses,
    successRate: (stats.successfulRequests / stats.totalRequests * 100).toFixed(2) + '%'
  });
}, 60000); // Every minute

Migration Guide

From Legacy github-cli-safe.js

// Old way (deprecated)
import { gh, safeGhCommand } from './github-cli-safe.js';

await gh.issueComment(123, 'Comment');
await safeGhCommand('pr comment', '456', 'PR comment');

// New way (recommended)
import { githubCli } from './github-cli-safety-wrapper.js';

await githubCli.addIssueComment(123, 'Comment');
await githubCli.addPRComment(456, 'PR comment');

From Direct gh CLI Usage

// Old way (unsafe)
import { execSync } from 'child_process';
execSync(`gh issue comment 123 --body "${unsafeBody}"`);

// New way (safe)
import { githubCli } from './github-cli-safety-wrapper.js';
await githubCli.addIssueComment(123, unsafeBody);

Best Practices

1. Always Use Proper Error Handling

try {
  const result = await githubCli.createPR({
    title: 'Feature: New component',
    body: prBody
  });
  console.log('PR created successfully:', result.stdout);
} catch (error) {
  console.error('Failed to create PR:', error.message);
  
  // Log for debugging
  if (error.details) {
    console.debug('Error details:', error.details);
  }
}

2. Validate Inputs Before Processing

async function createIssueFromUserInput(userInput) {
  // Validate required fields
  if (!userInput.title || !userInput.body) {
    throw new Error('Title and body are required');
  }
  
  // The wrapper will handle sanitization, but you can add business logic validation
  if (userInput.title.length > 256) {
    throw new Error('Title too long (max 256 characters)');
  }
  
  return await githubCli.createIssue(userInput);
}

3. Use Appropriate Timeouts

// Short timeout for simple operations
await githubCli.execute('auth status', { timeout: 5000 });

// Longer timeout for complex operations
await githubCli.createPR({
  title: 'Large refactor',
  body: veryLargeBody,
  timeout: 60000  // 1 minute
});

4. Monitor Usage and Performance

// Set up monitoring
const ghSafe = new GitHubCliSafe({ enableLogging: true });

// Periodic health checks
setInterval(async () => {
  try {
    await ghSafe.checkAuthentication();
    console.log('✅ GitHub CLI health check passed');
  } catch (error) {
    console.error('❌ GitHub CLI health check failed:', error.message);
  }
}, 300000); // Every 5 minutes

Troubleshooting

Common Issues

Authentication Problems

# Check authentication status
gh auth status

# Re-authenticate if needed
gh auth login

Permission Issues

# Ensure proper scopes
gh auth refresh -s repo -s admin:org

Rate Limiting

// Use custom rate limits for high-volume usage
const ghSafe = new GitHubCliSafe({
  enableRateLimit: true,
  maxRequestsPerWindow: 10,  // Very conservative
  rateLimitWindow: 60000     // 1 minute
});

Timeout Issues

// Increase timeout for slow networks or large operations
const result = await githubCli.createPR({
  title: 'Large PR',
  body: largeBody,
  timeout: 300000  // 5 minutes
});

Debug Mode

Enable detailed logging to troubleshoot issues:

const debugGhSafe = new GitHubCliSafe({
  enableLogging: true,
  timeout: 30000
});

// This will log detailed information about execution
await debugGhSafe.execute('issue create', { 
  title: 'Debug test',
  body: 'Testing debug mode'
});

Contributing

When contributing to the GitHub CLI Safety Wrapper:

  1. Add tests for any new functionality
  2. Update documentation for API changes
  3. Follow security best practices - all inputs must be validated
  4. Maintain backward compatibility where possible
  5. Add proper error handling for new error scenarios

Running Tests

npm test -- --testPathPattern=github-cli-safety-wrapper.test.js

Security Considerations

  • Never bypass input validation
  • Always use secure temp file handling
  • Implement proper cleanup for all resources
  • Test with malicious inputs to ensure safety
  • Monitor for new injection attack vectors

License

MIT License - see the main project LICENSE file for details.