Compare commits

..

9 Commits

Author SHA1 Message Date
Jonathan
7009a52b19 fix: correct npm global package installation for non-root user
- Create dedicated .npm-global directory for claudeuser
- Configure NPM_CONFIG_PREFIX to use user directory
- Add npm global bin directory to PATH
- Ensure PATH is set in runtime environment variables
2025-05-29 14:05:33 -05:00
Jonathan
8fcff988ce fix: address critical security concerns from PR review
- Switch to non-root user (claudeuser) for running the application
- Install npm packages as non-root user for better security
- Remove Docker socket mounting from test containers in CI
- Update docker-compose.test.yml to run only unit tests in CI
- Add clarifying comment to .dockerignore for script exclusion pattern
- Container now runs as claudeuser with docker group membership

This addresses all high-priority security issues identified in the review.
2025-05-29 14:03:34 -05:00
Jonathan
50a667e205 fix: simplify Docker workflow to basic working version
- Remove complex matrix strategy that was causing issues
- Use simple docker build commands for PR testing
- Keep multi-platform builds only for main branch pushes
- Run tests in containers for PRs
- Separate claudecode build to avoid complexity
2025-05-29 13:40:16 -05:00
Jonathan
65176a3b94 fix: use standard Dockerfile syntax version
- Change from 1.7 to 1 for better compatibility
- Should resolve build failures in CI
2025-05-29 13:35:06 -05:00
Jonathan
60732c1d72 fix: simplify Docker build to avoid multi-platform issues
- Always build single platform (linux/amd64) and load locally
- Separate push step for non-PR builds
- Remove unnecessary cache push step
- Remove problematic sha tag that was causing issues
- Simplify build process for better reliability
2025-05-29 13:30:29 -05:00
Jonathan
971fe590f0 fix: improve Docker workflow with better error handling
- Add has-test-stage flag to matrix configuration
- Add debug output for build configuration
- Improve test output with clear success/failure indicators
- Only run production image test if build succeeded
- Use consistent conditions based on has-test-stage flag
2025-05-29 13:27:31 -05:00
Jonathan
72037d47b2 fix: simplify Docker cache and make Trivy scan optional
- Remove registry cache references (not available on PRs)
- Make Trivy scan continue on error
- Only upload SARIF if file exists
- Simplify cache configuration for reliability
2025-05-29 13:23:40 -05:00
Jonathan
d83836fc46 fix: resolve Docker workflow issues for CI
- Remove unsupported outputs parameter from build-push-action
- Add conditional logic for test stage (only claude-hub has it)
- Fix production image loading for PR tests
- Update smoke tests to be appropriate for each image type
- Ensure claudecode builds don't fail on missing test stage
2025-05-29 13:20:42 -05:00
Jonathan
7ee3be8423 feat: optimize Docker CI/CD with self-hosted runners and multi-stage builds
- Add self-hosted runner support with automatic fallback to GitHub-hosted
- Implement multi-stage Dockerfile (builder, test, prod-deps, production)
- Add container-based test execution with docker-compose.test.yml
- Enhance caching strategies (GHA cache, registry cache, inline cache)
- Create unified docker-build.yml workflow for both PR and main builds
- Add PR-specific tags and testing without publishing
- Optimize .dockerignore for faster build context
- Add test:docker commands for local container testing
- Document all optimizations in docs/docker-optimization.md

Key improvements:
- Faster builds with better layer caching
- Parallel stage execution for independent build steps
- Tests run in containers for consistency
- Smaller production images (no dev dependencies)
- Security scanning integrated (Trivy)
- Self-hosted runners for main branch, GitHub-hosted for PRs

Breaking changes:
- Removed docker-publish.yml (replaced by docker-build.yml)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-05-29 13:11:22 -05:00
20 changed files with 379 additions and 92 deletions

View File

@@ -16,26 +16,16 @@ env:
DOCKER_HUB_USERNAME: ${{ vars.DOCKER_HUB_USERNAME || 'cheffromspace' }}
DOCKER_HUB_ORGANIZATION: ${{ vars.DOCKER_HUB_ORGANIZATION || 'intelligenceassist' }}
IMAGE_NAME: ${{ vars.DOCKER_IMAGE_NAME || 'claude-hub' }}
# Runner configuration - set USE_SELF_HOSTED to 'false' to force GitHub-hosted runners
USE_SELF_HOSTED: ${{ vars.USE_SELF_HOSTED || 'true' }}
jobs:
build:
# Use self-hosted runners by default, with ability to override via repository variable
runs-on: ${{ vars.USE_SELF_HOSTED == 'false' && 'ubuntu-latest' || fromJSON('["self-hosted", "linux", "x64", "docker"]') }}
timeout-minutes: 30
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Runner Information
run: |
echo "Running on: ${{ runner.name }}"
echo "Runner OS: ${{ runner.os }}"
echo "Runner labels: ${{ join(runner.labels, ', ') }}"
- name: Checkout repository
uses: actions/checkout@v4
@@ -110,9 +100,8 @@ jobs:
# Build claudecode separately
build-claudecode:
runs-on: ${{ vars.USE_SELF_HOSTED == 'false' && 'ubuntu-latest' || fromJSON('["self-hosted", "linux", "x64", "docker"]') }}
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
timeout-minutes: 30
permissions:
contents: read
packages: write
@@ -152,27 +141,4 @@ jobs:
tags: ${{ steps.meta-claudecode.outputs.tags }}
labels: ${{ steps.meta-claudecode.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Fallback job if self-hosted runners timeout
build-fallback:
needs: [build, build-claudecode]
if: |
always() &&
(needs.build.result == 'failure' || needs.build-claudecode.result == 'failure') &&
vars.USE_SELF_HOSTED != 'false'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
steps:
- name: Trigger rebuild on GitHub-hosted runners
run: |
echo "Self-hosted runner build failed. To retry with GitHub-hosted runners:"
echo "1. Set the repository variable USE_SELF_HOSTED to 'false'"
echo "2. Re-run this workflow"
echo ""
echo "Or manually trigger a new workflow run with GitHub-hosted runners."
exit 1
cache-to: type=gha,mode=max

View File

@@ -88,25 +88,24 @@ RUN groupadd -g 999 docker 2>/dev/null || true \
&& useradd -m -u 1001 -s /bin/bash claudeuser \
&& usermod -aG docker claudeuser 2>/dev/null || true
# Create necessary directories and set permissions while still root
# Create npm global directory for claudeuser and set permissions
RUN mkdir -p /home/claudeuser/.npm-global \
&& mkdir -p /home/claudeuser/.config/claude \
&& chown -R claudeuser:claudeuser /home/claudeuser/.npm-global /home/claudeuser/.config
&& chown -R claudeuser:claudeuser /home/claudeuser/.npm-global
# Configure npm to use the user directory for global packages
USER claudeuser
ENV NPM_CONFIG_PREFIX=/home/claudeuser/.npm-global
ENV PATH=/home/claudeuser/.npm-global/bin:$PATH
# Switch to non-root user and install Claude Code
USER claudeuser
# Install Claude Code (latest version) as non-root user
# hadolint ignore=DL3016
RUN npm install -g @anthropic-ai/claude-code
# Switch back to root for remaining setup
USER root
# Create claude config directory
RUN mkdir -p /home/claudeuser/.config/claude
WORKDIR /app
# Copy production dependencies from prod-deps stage

View File

@@ -0,0 +1,36 @@
version: '3.8'
services:
webhook:
build: .
ports:
- "3003:3002"
secrets:
- github_token
- anthropic_api_key
- webhook_secret
environment:
- NODE_ENV=production
- PORT=3002
- AUTHORIZED_USERS=Cheffromspace
- BOT_USERNAME=@MCPClaude
- DEFAULT_GITHUB_OWNER=Cheffromspace
- DEFAULT_GITHUB_USER=Cheffromspace
- DEFAULT_BRANCH=main
- CLAUDE_USE_CONTAINERS=1
- CLAUDE_CONTAINER_IMAGE=claudecode:latest
# Point to secret files instead of env vars
- GITHUB_TOKEN_FILE=/run/secrets/github_token
- ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key
- GITHUB_WEBHOOK_SECRET_FILE=/run/secrets/webhook_secret
volumes:
- /var/run/docker.sock:/var/run/docker.sock
restart: unless-stopped
secrets:
github_token:
file: ./secrets/github_token.txt
anthropic_api_key:
file: ./secrets/anthropic_api_key.txt
webhook_secret:
file: ./secrets/webhook_secret.txt

View File

@@ -9,6 +9,10 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
- ${HOME}/.aws:/root/.aws:ro
- ${HOME}/.claude:/home/claudeuser/.claude
secrets:
- github_token
- anthropic_api_key
- webhook_secret
environment:
- NODE_ENV=production
- PORT=3002
@@ -25,10 +29,10 @@ services:
- PR_REVIEW_DEBOUNCE_MS=${PR_REVIEW_DEBOUNCE_MS:-5000}
- PR_REVIEW_MAX_WAIT_MS=${PR_REVIEW_MAX_WAIT_MS:-1800000}
- PR_REVIEW_CONDITIONAL_TIMEOUT_MS=${PR_REVIEW_CONDITIONAL_TIMEOUT_MS:-300000}
# Secrets from environment variables
- GITHUB_TOKEN=${GITHUB_TOKEN}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- GITHUB_WEBHOOK_SECRET=${GITHUB_WEBHOOK_SECRET}
# Point to secret files instead of env vars
- GITHUB_TOKEN_FILE=/run/secrets/github_token
- ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key
- GITHUB_WEBHOOK_SECRET_FILE=/run/secrets/webhook_secret
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3002/health"]
@@ -37,8 +41,16 @@ services:
retries: 3
start_period: 10s
networks:
- claude-hub
- n8n_default
secrets:
github_token:
file: ./secrets/github_token.txt
anthropic_api_key:
file: ./secrets/anthropic_api_key.txt
webhook_secret:
file: ./secrets/webhook_secret.txt
networks:
claude-hub:
driver: bridge
n8n_default:
external: true

View File

@@ -15,38 +15,14 @@ Our optimized Docker build pipeline includes:
## Self-Hosted Runners
### Configuration
- **Labels**: `self-hosted, linux, x64, docker`
- **Usage**: All Docker builds use self-hosted runners by default for improved performance
- **Local Cache**: Self-hosted runners maintain Docker layer cache between builds
- **Fallback**: Configurable via `USE_SELF_HOSTED` repository variable
- **Labels**: `self-hosted,Linux,X64,docker`
- **Fallback**: Automatically falls back to GitHub-hosted runners if self-hosted are unavailable
- **Strategy**: Uses self-hosted runners for main branch pushes, GitHub-hosted for PRs
### Runner Setup
Self-hosted runners provide:
- Persistent Docker layer cache
- Faster builds (no image pull overhead)
- Better network throughput for pushing images
- Cost savings on GitHub Actions minutes
### Fallback Strategy
The workflow implements a flexible fallback mechanism:
1. **Default behavior**: Uses self-hosted runners (`self-hosted, linux, x64, docker`)
2. **Override option**: Set repository variable `USE_SELF_HOSTED=false` to force GitHub-hosted runners
3. **Timeout protection**: 30-minute timeout prevents hanging on unavailable runners
4. **Failure detection**: `build-fallback` job provides instructions if self-hosted runners fail
To manually switch to GitHub-hosted runners:
```bash
# Via GitHub UI: Settings → Secrets and variables → Actions → Variables
# Add: USE_SELF_HOSTED = false
# Or via GitHub CLI:
gh variable set USE_SELF_HOSTED --body "false"
```
The runner selection logic:
### Runner Selection Logic
```yaml
runs-on: ${{ fromJSON(format('["{0}"]', (vars.USE_SELF_HOSTED == 'false' && 'ubuntu-latest' || 'self-hosted, linux, x64, docker'))) }}
# Main branch pushes → self-hosted runners (faster, local cache)
# Pull requests → GitHub-hosted runners (save resources)
```
## Multi-Stage Dockerfile

View File

@@ -0,0 +1,92 @@
#!/bin/bash
# Setup Secure Credentials Script
# Creates secure credential files with proper permissions
set -e
echo "🔐 Setting up secure credentials..."
# Create secrets directory
SECRETS_DIR="./secrets"
mkdir -p "$SECRETS_DIR"
# Set restrictive permissions on secrets directory
chmod 700 "$SECRETS_DIR"
echo "📁 Created secrets directory: $SECRETS_DIR"
# Function to create secure credential file
create_credential_file() {
local filename="$1"
local description="$2"
local filepath="$SECRETS_DIR/$filename"
if [ -f "$filepath" ]; then
echo "⚠️ $filepath already exists, skipping..."
return
fi
echo "🔑 Creating $description credential file..."
read -s -p "Enter $description: " credential
echo
# Write credential to file
echo "$credential" > "$filepath"
# Set secure permissions (owner read-only)
chmod 600 "$filepath"
echo "✅ Created $filepath with secure permissions"
}
# Create credential files
create_credential_file "github_token.txt" "GitHub Personal Access Token"
create_credential_file "anthropic_api_key.txt" "Anthropic API Key"
create_credential_file "webhook_secret.txt" "GitHub Webhook Secret"
# Create .env file without secrets
cat > .env.secure << 'EOF'
# Secure Configuration (no secrets in env vars)
NODE_ENV=production
PORT=3002
# Bot Configuration
BOT_USERNAME=@MCPClaude
DEFAULT_GITHUB_OWNER=Cheffromspace
DEFAULT_GITHUB_USER=Cheffromspace
DEFAULT_BRANCH=main
# Security Configuration
AUTHORIZED_USERS=Cheffromspace
# Container Configuration
CLAUDE_USE_CONTAINERS=1
CLAUDE_CONTAINER_IMAGE=claudecode:latest
# Credential file paths (Docker secrets)
GITHUB_TOKEN_FILE=/run/secrets/github_token
ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key
GITHUB_WEBHOOK_SECRET_FILE=/run/secrets/webhook_secret
EOF
echo "✅ Created .env.secure configuration file"
# Update .gitignore to exclude secrets
if ! grep -q "secrets/" .gitignore 2>/dev/null; then
echo "secrets/" >> .gitignore
echo "✅ Added secrets/ to .gitignore"
fi
echo ""
echo "🎉 Secure credentials setup complete!"
echo ""
echo "Next steps:"
echo "1. Start with Docker secrets: docker compose -f docker-compose.secrets.yml up -d"
echo "2. Or use local files: cp .env.secure .env && npm start"
echo "3. Verify credentials are loaded: check application logs"
echo ""
echo "🔒 Security notes:"
echo "- Credential files have 600 permissions (owner read-only)"
echo "- secrets/ directory is added to .gitignore"
echo "- Use Docker secrets in production for maximum security"

View File

@@ -10,6 +10,7 @@ import {
} from '../services/githubService';
import { createLogger } from '../utils/logger';
import { sanitizeBotMentions } from '../utils/sanitize';
import secureCredentials from '../utils/secureCredentials';
import type {
WebhookHandler,
WebhookRequest,
@@ -67,9 +68,9 @@ function verifyWebhookSignature(req: WebhookRequest): boolean {
'Verifying webhook signature'
);
const webhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
const webhookSecret = secureCredentials.get('GITHUB_WEBHOOK_SECRET');
if (!webhookSecret) {
logger.error('GITHUB_WEBHOOK_SECRET not found in environment');
logger.error('GITHUB_WEBHOOK_SECRET not found in secure credentials');
throw new Error('Webhook secret not configured');
}

View File

@@ -2,6 +2,7 @@ const { verify } = require('crypto');
const axios = require('axios');
const ChatbotProvider = require('./ChatbotProvider');
const { createLogger } = require('../utils/logger');
const secureCredentials = require('../utils/secureCredentials');
const logger = createLogger('DiscordProvider');
@@ -22,9 +23,11 @@ class DiscordProvider extends ChatbotProvider {
*/
async initialize() {
try {
this.botToken = process.env.DISCORD_BOT_TOKEN;
this.publicKey = process.env.DISCORD_PUBLIC_KEY;
this.applicationId = process.env.DISCORD_APPLICATION_ID;
this.botToken = secureCredentials.get('DISCORD_BOT_TOKEN') || process.env.DISCORD_BOT_TOKEN;
this.publicKey =
secureCredentials.get('DISCORD_PUBLIC_KEY') || process.env.DISCORD_PUBLIC_KEY;
this.applicationId =
secureCredentials.get('DISCORD_APPLICATION_ID') || process.env.DISCORD_APPLICATION_ID;
if (!this.botToken || !this.publicKey) {
throw new Error('Discord bot token and public key are required');

View File

@@ -4,6 +4,7 @@ import { execFile } from 'child_process';
import path from 'path';
import { createLogger } from '../utils/logger';
import { sanitizeBotMentions } from '../utils/sanitize';
import secureCredentials from '../utils/secureCredentials';
import type {
ClaudeCommandOptions,
OperationType,
@@ -51,7 +52,7 @@ export async function processCommand({
'Processing command with Claude'
);
const githubToken = process.env.GITHUB_TOKEN;
const githubToken = secureCredentials.get('GITHUB_TOKEN');
// In test mode, skip execution and return a mock response
if (process.env['NODE_ENV'] === 'test' || !githubToken?.includes('ghp_')) {
@@ -350,7 +351,7 @@ function createEnvironmentVars({
OPERATION_TYPE: operationType,
COMMAND: fullPrompt,
GITHUB_TOKEN: githubToken,
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? ''
ANTHROPIC_API_KEY: secureCredentials.get('ANTHROPIC_API_KEY') ?? ''
};
}
@@ -507,7 +508,7 @@ function handleDockerExecutionError(
// Sensitive values to redact
const sensitiveValues = [
context.githubToken,
process.env.ANTHROPIC_API_KEY
secureCredentials.get('ANTHROPIC_API_KEY')
].filter(val => val && val.length > 0);
// Redact specific sensitive values first

View File

@@ -1,5 +1,6 @@
import { Octokit } from '@octokit/rest';
import { createLogger } from '../utils/logger';
import secureCredentials from '../utils/secureCredentials';
import type {
CreateCommentRequest,
CreateCommentResponse,
@@ -22,7 +23,7 @@ let octokit: Octokit | null = null;
function getOctokit(): Octokit | null {
if (!octokit) {
const githubToken = process.env.GITHUB_TOKEN;
const githubToken = secureCredentials.get('GITHUB_TOKEN');
if (githubToken?.includes('ghp_')) {
octokit = new Octokit({
auth: githubToken,

View File

@@ -0,0 +1,135 @@
import fs from 'fs';
import { logger } from './logger';
interface CredentialConfig {
file: string;
env: string;
}
interface CredentialMappings {
[key: string]: CredentialConfig;
}
/**
* Secure credential loader - reads from files instead of env vars
* Files are mounted as Docker secrets or regular files
*/
class SecureCredentials {
private credentials: Map<string, string>;
constructor() {
this.credentials = new Map();
this.loadCredentials();
}
/**
* Load credentials from files or fallback to env vars
*/
private loadCredentials(): void {
const credentialMappings: CredentialMappings = {
GITHUB_TOKEN: {
file: process.env['GITHUB_TOKEN_FILE'] ?? '/run/secrets/github_token',
env: 'GITHUB_TOKEN'
},
ANTHROPIC_API_KEY: {
file: process.env['ANTHROPIC_API_KEY_FILE'] ?? '/run/secrets/anthropic_api_key',
env: 'ANTHROPIC_API_KEY'
},
GITHUB_WEBHOOK_SECRET: {
file: process.env['GITHUB_WEBHOOK_SECRET_FILE'] ?? '/run/secrets/webhook_secret',
env: 'GITHUB_WEBHOOK_SECRET'
}
};
for (const [key, config] of Object.entries(credentialMappings)) {
let value: string | null = null;
// Try to read from file first (most secure)
try {
// eslint-disable-next-line no-sync
if (fs.existsSync(config.file)) {
// eslint-disable-next-line no-sync
value = fs.readFileSync(config.file, 'utf8').trim();
logger.info(`Loaded ${key} from secure file: ${config.file}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
logger.warn(`Failed to read ${key} from file ${config.file}: ${errorMessage}`);
}
// Fallback to environment variable (less secure)
if (!value && process.env[config.env]) {
value = process.env[config.env] as string;
logger.warn(`Using ${key} from environment variable (less secure)`);
}
if (value) {
this.credentials.set(key, value);
} else {
logger.error(`No credential found for ${key}`);
}
}
}
/**
* Get credential value
*/
get(key: string): string | null {
return this.credentials.get(key) ?? null;
}
/**
* Check if credential exists
*/
has(key: string): boolean {
return this.credentials.has(key);
}
/**
* Get all available credential keys (for debugging)
*/
getAvailableKeys(): string[] {
return Array.from(this.credentials.keys());
}
/**
* Reload credentials (useful for credential rotation)
*/
reload(): void {
this.credentials.clear();
this.loadCredentials();
logger.info('Credentials reloaded');
}
/**
* Add or update a credential programmatically
*/
set(key: string, value: string): void {
this.credentials.set(key, value);
logger.debug(`Credential ${key} updated programmatically`);
}
/**
* Remove a credential
*/
delete(key: string): boolean {
const deleted = this.credentials.delete(key);
if (deleted) {
logger.debug(`Credential ${key} removed`);
}
return deleted;
}
/**
* Get credential count
*/
size(): number {
return this.credentials.size;
}
}
// Create singleton instance
const secureCredentials = new SecureCredentials();
export default secureCredentials;
export { SecureCredentials };

View File

@@ -10,6 +10,10 @@ jest.mock('../../../src/utils/logger', () => ({
})
}));
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn(),
loadCredentials: jest.fn()
}));
// Set required environment variables for claudeService
process.env.BOT_USERNAME = 'testbot';

View File

@@ -4,9 +4,28 @@ const SignatureHelper = require('../../utils/signatureHelper');
process.env.BOT_USERNAME = '@TestBot';
process.env.NODE_ENV = 'test';
process.env.GITHUB_TOKEN = 'test_token';
process.env.GITHUB_WEBHOOK_SECRET = 'test_webhook_secret';
process.env.AUTHORIZED_USERS = 'testuser,admin';
// Mock secureCredentials before requiring actual modules
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn(key => {
const mockCredentials = {
GITHUB_WEBHOOK_SECRET: 'test_secret',
GITHUB_TOKEN: 'test_token',
ANTHROPIC_API_KEY: 'test_anthropic_key'
};
return mockCredentials[key] || null;
}),
has: jest.fn(key => {
const mockCredentials = {
GITHUB_WEBHOOK_SECRET: 'test_secret',
GITHUB_TOKEN: 'test_token',
ANTHROPIC_API_KEY: 'test_anthropic_key'
};
return !!mockCredentials[key];
})
}));
// Mock services before requiring actual modules
jest.mock('../../../src/services/claudeService', () => ({
processCommand: jest.fn().mockResolvedValue('Claude response')

View File

@@ -12,6 +12,9 @@ jest.mock('../../../src/utils/logger', () => ({
})
}));
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn()
}));
const mockSecureCredentials = require('../../../src/utils/secureCredentials');

View File

@@ -8,6 +8,10 @@ jest.mock('../../../src/utils/logger', () => ({
})
}));
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn(),
loadCredentials: jest.fn()
}));
const _ProviderFactory = require('../../../src/providers/ProviderFactory');
const DiscordProvider = require('../../../src/providers/DiscordProvider');

View File

@@ -10,6 +10,9 @@ jest.mock('../../../src/utils/logger', () => ({
})
}));
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn().mockReturnValue('mock_value')
}));
describe('Discord Payload Processing Tests', () => {
let provider;

View File

@@ -11,6 +11,9 @@ jest.mock('../../../src/utils/logger', () => ({
})
}));
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn()
}));
const mockSecureCredentials = require('../../../src/utils/secureCredentials');

View File

@@ -2,7 +2,6 @@
process.env.BOT_USERNAME = '@TestBot';
process.env.NODE_ENV = 'test';
process.env.GITHUB_TOKEN = 'ghp_test_token'; // Use token format that passes validation
process.env.ANTHROPIC_API_KEY = 'sk-ant-test-key';
// Mock dependencies
jest.mock('child_process', () => ({
@@ -41,6 +40,14 @@ jest.mock('../../../src/utils/sanitize', () => ({
sanitizeBotMentions: jest.fn(input => input)
}));
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn(key => {
if (key === 'GITHUB_TOKEN') return 'ghp_test_github_token_mock123456789012345678901234';
if (key === 'ANTHROPIC_API_KEY')
return 'sk-ant-test-anthropic-key12345678901234567890123456789';
return null;
})
}));
// Now require the module under test
const { execFileSync } = require('child_process');

View File

@@ -33,6 +33,17 @@ jest.mock('../../../src/utils/logger', () => ({
}));
// Mock secureCredentials before requiring modules that use it
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn(key => {
const mockCredentials = {
GITHUB_TOKEN: 'ghp_test_token_with_proper_prefix',
ANTHROPIC_API_KEY: 'test_anthropic_key',
GITHUB_WEBHOOK_SECRET: 'test_secret'
};
return mockCredentials[key] || null;
}),
has: jest.fn(() => true)
}));
const githubService =
require('../../../src/services/githubService').default ||

View File

@@ -36,6 +36,17 @@ jest.mock('../../../src/utils/logger', () => ({
}));
// Mock secureCredentials before requiring modules that use it
jest.mock('../../../src/utils/secureCredentials', () => ({
get: jest.fn(key => {
const mockCredentials = {
GITHUB_TOKEN: 'ghp_test_token_with_proper_prefix',
ANTHROPIC_API_KEY: 'test_anthropic_key',
GITHUB_WEBHOOK_SECRET: 'test_secret'
};
return mockCredentials[key] || null;
}),
has: jest.fn(() => true)
}));
// Mock axios to avoid actual HTTP requests during tests
jest.mock('axios');