From dee794e586bbd0c382bcf7a387aa39a756dc3540 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:42:26 +0000 Subject: [PATCH 1/3] Initial plan From 1b3ad90058b8c58de9fd4766638342e9e05f4714 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:47:43 +0000 Subject: [PATCH 2/3] fix: address all PR review comments - remove duplicates, fix dates, remove unused imports/vars Co-authored-by: clduab11 <185000089+clduab11@users.noreply.github.com> --- PR9_FINAL_REPORT.md | 2 +- PR9_IMPROVEMENT_ROADMAP.md | 2 +- PR9_PROGRESS_REPORT.md | 2 +- PR9_QUICK_REFERENCE.md | 2 +- .../__tests__/gamification-service.test.ts | 4 +- .../src/__tests__/base-tool.test.ts | 2 +- .../src/__tests__/mcp-logging.test.ts | 1 - .../src/utils/__tests__/sanitization.test.ts | 377 ------------------ .../backend/shared/src/utils/sanitization.ts | 252 ------------ .../src/utils/__tests__/sanitization.test.ts | 3 +- backend/shared/src/utils/json-parser.ts | 9 +- backend/shared/src/utils/redis-utils.ts | 2 +- backend/shared/src/utils/sanitization.ts | 2 +- .../__tests__/mock-data-generator.test.ts | 3 +- scripts/check-comments.ts | 1 - scripts/check-doc-links.ts | 2 +- 16 files changed, 18 insertions(+), 648 deletions(-) delete mode 100644 backend/shared/backend/shared/src/utils/__tests__/sanitization.test.ts delete mode 100644 backend/shared/backend/shared/src/utils/sanitization.ts diff --git a/PR9_FINAL_REPORT.md b/PR9_FINAL_REPORT.md index 24db7be..2bf56b1 100644 --- a/PR9_FINAL_REPORT.md +++ b/PR9_FINAL_REPORT.md @@ -1,6 +1,6 @@ # PR #9 Final Production Report -**Date:** 2024-11-17 +**Date:** 2025-11-17 **Branch:** `claude/resolve-pr9-launch-01Qoi7se3KmWkugvd634NYhZ` **Status:** ✅ **100% COMPLETE - PRODUCTION READY** 🎉 diff --git a/PR9_IMPROVEMENT_ROADMAP.md b/PR9_IMPROVEMENT_ROADMAP.md index 151fc48..4c6db05 100644 --- a/PR9_IMPROVEMENT_ROADMAP.md +++ b/PR9_IMPROVEMENT_ROADMAP.md @@ -2,7 +2,7 @@ **Status:** Changes Requested **Created:** 2025-11-16 -**Priority:** CRITICAL - App Store Launch Deadline: Nov 18, 11:59 PM CST +**Priority:** CRITICAL - App Store Launch Deadline: 2025-11-18, 11:59 PM CST **Branch:** `claude/resolve-pr9-launch-01Qoi7se3KmWkugvd634NYhZ` --- diff --git a/PR9_PROGRESS_REPORT.md b/PR9_PROGRESS_REPORT.md index 3d38843..9efc4b8 100644 --- a/PR9_PROGRESS_REPORT.md +++ b/PR9_PROGRESS_REPORT.md @@ -1,6 +1,6 @@ # PR #9 Implementation Progress Report -**Date:** 2024-11-16 +**Date:** 2025-11-16 **Branch:** `claude/resolve-pr9-launch-01Qoi7se3KmWkugvd634NYhZ` **Session:** Implementation Phase 2 **Status:** ✅ 83% Complete (5/6 CRITICAL+MAJOR issues) diff --git a/PR9_QUICK_REFERENCE.md b/PR9_QUICK_REFERENCE.md index 52fa181..6be2585 100644 --- a/PR9_QUICK_REFERENCE.md +++ b/PR9_QUICK_REFERENCE.md @@ -1,7 +1,7 @@ # PR #9 Quick Reference Guide **Last Updated:** 2025-11-16 -**Deadline:** Nov 18, 11:59 PM CST (App Store Launch) +**Deadline:** 2025-11-18, 11:59 PM CST (App Store Launch) **Branch:** `claude/resolve-pr9-launch-01Qoi7se3KmWkugvd634NYhZ` --- diff --git a/backend/services/game-service/src/__tests__/gamification-service.test.ts b/backend/services/game-service/src/__tests__/gamification-service.test.ts index 6139157..c012db2 100644 --- a/backend/services/game-service/src/__tests__/gamification-service.test.ts +++ b/backend/services/game-service/src/__tests__/gamification-service.test.ts @@ -10,9 +10,7 @@ import { calculatePointsDetailed, calculatePointsWithDecay, GamificationService, - DEFAULT_SCORING_CONFIG, - ChallengeResult, - ScoringResult + ChallengeResult } from '../services/gamification-service'; describe('GamificationService - Point Calculation', () => { diff --git a/backend/services/mcp-server/src/__tests__/base-tool.test.ts b/backend/services/mcp-server/src/__tests__/base-tool.test.ts index 8c11f82..9e35896 100644 --- a/backend/services/mcp-server/src/__tests__/base-tool.test.ts +++ b/backend/services/mcp-server/src/__tests__/base-tool.test.ts @@ -5,7 +5,7 @@ * @see Issue #6: MAJOR - MCP Server Sensitive Data Logging */ -import { BaseMCPTool, ExampleTool, ToolExecutionResult } from '../tools/base-tool'; +import { BaseMCPTool, ExampleTool } from '../tools/base-tool'; // Test implementation of BaseMCPTool class TestTool extends BaseMCPTool<{ message: string }, { result: string }> { diff --git a/backend/services/mcp-server/src/__tests__/mcp-logging.test.ts b/backend/services/mcp-server/src/__tests__/mcp-logging.test.ts index 2bb5259..9d86e7a 100644 --- a/backend/services/mcp-server/src/__tests__/mcp-logging.test.ts +++ b/backend/services/mcp-server/src/__tests__/mcp-logging.test.ts @@ -9,7 +9,6 @@ import { sanitizeMCPToolArgs, sanitizeMCPResponse, MCP_SENSITIVE_FIELDS, - MCP_SANITIZATION_CONFIG, mcpLoggingMiddleware, createLoggingContext, logToolInvocation, diff --git a/backend/shared/backend/shared/src/utils/__tests__/sanitization.test.ts b/backend/shared/backend/shared/src/utils/__tests__/sanitization.test.ts deleted file mode 100644 index db75344..0000000 --- a/backend/shared/backend/shared/src/utils/__tests__/sanitization.test.ts +++ /dev/null @@ -1,377 +0,0 @@ -/** - * Comprehensive test suite for sanitization utilities - * Ensures PII protection and GDPR/CCPA compliance - * - * @see backend/shared/src/utils/sanitization.ts - */ - -import { - sanitizeToolArgs, - detectPII, - sanitizeString, - createSanitizationConfig, - sanitizeError, - DEFAULT_CONFIG -} from '../sanitization'; - -describe('Sanitization Utils', () => { - describe('sanitizeToolArgs', () => { - it('should redact email addresses', () => { - const input = { email: 'user@example.com', name: 'John' }; - const output = sanitizeToolArgs(input); - - expect(output.email).toBe('[REDACTED]'); - expect(output.name).toBe('John'); - }); - - it('should redact nested sensitive fields', () => { - const input = { - user: { - email: 'user@example.com', - profile: { - apiKey: 'secret-key-123', - preferences: { theme: 'dark' } - } - } - }; - - const output = sanitizeToolArgs(input); - - expect(output.user.email).toBe('[REDACTED]'); - expect(output.user.profile.apiKey).toBe('[REDACTED]'); - expect(output.user.profile.preferences.theme).toBe('dark'); - }); - - it('should handle arrays of objects', () => { - const input = { - users: [ - { email: 'user1@example.com', name: 'Alice' }, - { email: 'user2@example.com', name: 'Bob' } - ] - }; - - const output = sanitizeToolArgs(input); - - expect(output.users[0].email).toBe('[REDACTED]'); - expect(output.users[0].name).toBe('Alice'); - expect(output.users[1].email).toBe('[REDACTED]'); - expect(output.users[1].name).toBe('Bob'); - }); - - it('should redact authentication tokens', () => { - const input = { - accessToken: 'jwt-token-123', - refreshToken: 'refresh-456', - data: { value: 'public' } - }; - - const output = sanitizeToolArgs(input); - - expect(output.accessToken).toBe('[REDACTED]'); - expect(output.refreshToken).toBe('[REDACTED]'); - expect(output.data.value).toBe('public'); - }); - - it('should handle null and undefined values', () => { - const input = { value: null, optional: undefined, name: 'test' }; - const output = sanitizeToolArgs(input); - - expect(output.value).toBeNull(); - expect(output.optional).toBeUndefined(); - expect(output.name).toBe('test'); - }); - - it('should prevent infinite recursion with max depth', () => { - const input = { level1: { level2: { level3: { level4: { level5: { level6: 'deep' } } } } } }; - const output = sanitizeToolArgs(input); - - // Should stop at maxDepth (default 5) - expect(output.level1.level2.level3.level4.level5.level6).toBe('[MAX_DEPTH_EXCEEDED]'); - }); - - it('should handle circular references gracefully', () => { - const input: any = { name: 'test' }; - input.self = input; // Circular reference - - // Should not throw and handle gracefully - expect(() => sanitizeToolArgs(input)).not.toThrow(); - }); - - it('should handle primitive values', () => { - expect(sanitizeToolArgs('string')).toBe('string'); - expect(sanitizeToolArgs(123)).toBe(123); - expect(sanitizeToolArgs(true)).toBe(true); - expect(sanitizeToolArgs(null)).toBeNull(); - }); - - it('should use custom configuration', () => { - const input = { customField: 'secret', normalField: 'public' }; - const config = { - sensitiveFields: ['customField'], - redactionText: '[CUSTOM_REDACTED]' - }; - - const output = sanitizeToolArgs(input, config); - - expect(output.customField).toBe('[CUSTOM_REDACTED]'); - expect(output.normalField).toBe('public'); - }); - - it('should handle case-insensitive field matching', () => { - const input = { - EMAIL: 'upper@example.com', - Email: 'mixed@example.com', - email: 'lower@example.com' - }; - - const output = sanitizeToolArgs(input); - - expect(output.EMAIL).toBe('[REDACTED]'); - expect(output.Email).toBe('[REDACTED]'); - expect(output.email).toBe('[REDACTED]'); - }); - - it('should handle Date objects', () => { - const date = new Date('2024-01-01'); - const input = { timestamp: date, name: 'test' }; - - const output = sanitizeToolArgs(input); - - expect(output.timestamp).toEqual(date); - expect(output.name).toBe('test'); - }); - }); - - describe('detectPII', () => { - it('should detect email addresses', () => { - expect(detectPII('Contact: user@example.com')).toBe(true); - expect(detectPII('Email me at john.doe@company.org')).toBe(true); - }); - - it('should detect phone numbers', () => { - expect(detectPII('Call: 555-123-4567')).toBe(true); - expect(detectPII('Phone: (555) 123-4567')).toBe(true); - expect(detectPII('+1-555-123-4567')).toBe(true); - }); - - it('should detect SSN', () => { - expect(detectPII('SSN: 123-45-6789')).toBe(true); - }); - - it('should detect credit card numbers', () => { - expect(detectPII('Card: 1234 5678 9012 3456')).toBe(true); - expect(detectPII('CC: 1234-5678-9012-3456')).toBe(true); - }); - - it('should detect JWT tokens', () => { - const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U'; - expect(detectPII(jwt)).toBe(true); - }); - - it('should return false for clean text', () => { - expect(detectPII('This is clean text')).toBe(false); - expect(detectPII('No sensitive information here')).toBe(false); - }); - - it('should detect IP addresses', () => { - expect(detectPII('IP: 192.168.1.1')).toBe(true); - expect(detectPII('Server at 10.0.0.1')).toBe(true); - }); - }); - - describe('sanitizeString', () => { - it('should redact emails in text', () => { - const input = 'Contact us at support@example.com or sales@example.org'; - const output = sanitizeString(input); - - expect(output).not.toContain('support@example.com'); - expect(output).not.toContain('sales@example.org'); - expect(output).toContain('[EMAIL_REDACTED]'); - }); - - it('should redact phone numbers in text', () => { - const input = 'Call 555-123-4567 or (555) 987-6543'; - const output = sanitizeString(input); - - expect(output).not.toContain('555-123-4567'); - expect(output).toContain('[PHONE_REDACTED]'); - }); - - it('should redact multiple PII types', () => { - const input = 'Email: user@test.com, Phone: 555-1234, SSN: 123-45-6789'; - const output = sanitizeString(input); - - expect(output).toContain('[EMAIL_REDACTED]'); - expect(output).toContain('[PHONE_REDACTED]'); - expect(output).toContain('[SSN_REDACTED]'); - expect(output).not.toContain('user@test.com'); - expect(output).not.toContain('123-45-6789'); - }); - - it('should preserve non-PII content', () => { - const input = 'Hello, this is a message with user@test.com embedded'; - const output = sanitizeString(input); - - expect(output).toContain('Hello'); - expect(output).toContain('this is a message'); - expect(output).not.toContain('user@test.com'); - }); - }); - - describe('createSanitizationConfig', () => { - it('should create config with additional fields', () => { - const config = createSanitizationConfig(['customSecret', 'internalId']); - - expect(config.sensitiveFields).toContain('customSecret'); - expect(config.sensitiveFields).toContain('internalId'); - expect(config.sensitiveFields).toContain('email'); // Default fields still included - }); - - it('should allow custom redaction text', () => { - const config = createSanitizationConfig([], '[HIDDEN]'); - - expect(config.redactionText).toBe('[HIDDEN]'); - }); - }); - - describe('sanitizeError', () => { - it('should sanitize Error objects', () => { - const error = new Error('Database error: user@example.com not found'); - const sanitized = sanitizeError(error); - - expect(sanitized.name).toBe('Error'); - expect(sanitized.message).not.toContain('user@example.com'); - expect(sanitized.message).toContain('[EMAIL_REDACTED]'); - }); - - it('should sanitize error-like objects', () => { - const error = { - code: 'AUTH_FAILED', - email: 'user@example.com', - token: 'secret-token' - }; - - const sanitized = sanitizeError(error); - - expect(sanitized.code).toBe('AUTH_FAILED'); - expect(sanitized.email).toBe('[REDACTED]'); - expect(sanitized.token).toBe('[REDACTED]'); - }); - - it('should remove stack traces in production', () => { - const originalEnv = process.env.NODE_ENV; - process.env.NODE_ENV = 'production'; - - const error = new Error('Test error'); - const sanitized = sanitizeError(error); - - expect(sanitized.stack).toBeUndefined(); - - process.env.NODE_ENV = originalEnv; - }); - - it('should include stack traces in development', () => { - const originalEnv = process.env.NODE_ENV; - process.env.NODE_ENV = 'development'; - - const error = new Error('Test error'); - const sanitized = sanitizeError(error); - - expect(sanitized.stack).toBeDefined(); - - process.env.NODE_ENV = originalEnv; - }); - }); - - describe('Integration: Real-world scenarios', () => { - it('should handle complex authentication payload', () => { - const payload = { - username: 'john_doe', - password: 'super-secret-123', - email: 'john@example.com', - token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', - metadata: { - ipAddress: '192.168.1.1', - userAgent: 'Mozilla/5.0', - sessionId: 'abc123' - } - }; - - const sanitized = sanitizeToolArgs(payload); - - expect(sanitized.username).toBe('john_doe'); - expect(sanitized.password).toBe('[REDACTED]'); - expect(sanitized.email).toBe('[REDACTED]'); - expect(sanitized.token).toBe('[REDACTED]'); - expect(sanitized.metadata.userAgent).toBe('Mozilla/5.0'); - expect(sanitized.metadata.sessionId).toBe('[REDACTED]'); - }); - - it('should handle payment information', () => { - const payment = { - amount: 99.99, - currency: 'USD', - creditCard: '1234-5678-9012-3456', - cvv: '123', - billingAddress: '123 Main St' - }; - - const sanitized = sanitizeToolArgs(payment); - - expect(sanitized.amount).toBe(99.99); - expect(sanitized.currency).toBe('USD'); - expect(sanitized.creditCard).toBe('[REDACTED]'); - expect(sanitized.cvv).toBe('[REDACTED]'); - expect(sanitized.billingAddress).toBe('[REDACTED]'); - }); - - it('should handle API request logs', () => { - const apiLog = { - method: 'POST', - path: '/api/users', - headers: { - authorization: 'Bearer eyJhbGci...', - 'content-type': 'application/json' - }, - body: { - email: 'newuser@example.com', - password: 'password123' - } - }; - - const sanitized = sanitizeToolArgs(apiLog); - - expect(sanitized.method).toBe('POST'); - expect(sanitized.path).toBe('/api/users'); - expect(sanitized.headers.authorization).toBe('[REDACTED]'); - expect(sanitized.headers['content-type']).toBe('application/json'); - expect(sanitized.body.email).toBe('[REDACTED]'); - expect(sanitized.body.password).toBe('[REDACTED]'); - }); - }); - - describe('Performance', () => { - it('should handle large objects efficiently', () => { - const largeObject: any = {}; - for (let i = 0; i < 1000; i++) { - largeObject[`field${i}`] = i % 2 === 0 ? `value${i}` : `email${i}@example.com`; - } - - const start = Date.now(); - const sanitized = sanitizeToolArgs(largeObject); - const duration = Date.now() - start; - - expect(duration).toBeLessThan(100); // Should complete in <100ms - expect(Object.keys(sanitized).length).toBe(1000); - }); - - it('should handle deeply nested structures', () => { - let deep: any = { value: 'test' }; - for (let i = 0; i < 10; i++) { - deep = { nested: deep }; - } - - expect(() => sanitizeToolArgs(deep)).not.toThrow(); - }); - }); -}); diff --git a/backend/shared/backend/shared/src/utils/sanitization.ts b/backend/shared/backend/shared/src/utils/sanitization.ts deleted file mode 100644 index 5e17247..0000000 --- a/backend/shared/backend/shared/src/utils/sanitization.ts +++ /dev/null @@ -1,252 +0,0 @@ -/** - * Sanitization utilities for PII and sensitive data protection - * - * This module provides comprehensive data sanitization for logging and external communication, - * ensuring GDPR/CCPA compliance by redacting personally identifiable information (PII). - * - * @module sanitization - * @see CRITICAL-02-pii-logging-exposure.md - * @created 2024-11-16 - * @updated 2024-11-16 - */ - -export interface SanitizationConfig { - /** List of field names considered sensitive */ - sensitiveFields: string[]; - /** Text to use for redacted values */ - redactionText: string; - /** Maximum object depth to traverse */ - maxDepth: number; -} - -export const DEFAULT_CONFIG: SanitizationConfig = { - sensitiveFields: [ - // Authentication & Authorization - 'password', 'passwd', 'pwd', - 'token', 'accessToken', 'refreshToken', 'access_token', 'refresh_token', - 'apiKey', 'api_key', 'secret', 'secretKey', 'secret_key', - 'authorization', 'auth', 'bearer', - 'jwt', 'session', 'sessionId', 'session_id', - 'cookie', 'cookies', - - // Personal Information - 'email', 'emailAddress', 'email_address', - 'phone', 'phoneNumber', 'phone_number', 'mobile', - 'ssn', 'social_security', 'socialSecurity', - 'address', 'street', 'city', 'zipcode', 'zip', 'postal', - 'dob', 'dateOfBirth', 'date_of_birth', 'birthday', - - // Financial - 'creditCard', 'credit_card', 'cardNumber', 'card_number', - 'cvv', 'cvc', 'expiry', 'expiryDate', - 'bankAccount', 'bank_account', 'accountNumber', 'account_number', - 'routing', 'routingNumber', 'routing_number', - - // Identifiers - 'userId', 'user_id', 'username', - 'clientId', 'client_id', 'clientSecret', 'client_secret', - - // AI/MCP Specific - 'mcp_token', 'model_api_key', 'context_data', 'user_context' - ], - redactionText: '[REDACTED]', - maxDepth: 5 -}; - -/** - * Sanitizes data by redacting sensitive fields - * - * @param data - Data to sanitize (any type) - * @param config - Optional configuration override - * @returns Sanitized copy of data with sensitive fields redacted - * - * @example - * ```typescript - * const data = { email: 'user@example.com', name: 'John' }; - * const safe = sanitizeToolArgs(data); - * // Result: { email: '[REDACTED]', name: 'John' } - * ``` - */ -export function sanitizeToolArgs( - data: any, - config: Partial = {} -): any { - const finalConfig = { ...DEFAULT_CONFIG, ...config }; - return sanitizeObject(data, finalConfig, 0); -} - -/** - * Internal recursive sanitization function - * - * @param obj - Object to sanitize - * @param config - Sanitization configuration - * @param depth - Current recursion depth - * @returns Sanitized object - */ -function sanitizeObject( - obj: any, - config: SanitizationConfig, - depth: number -): any { - // Depth protection - if (depth > config.maxDepth) { - return '[MAX_DEPTH_EXCEEDED]'; - } - - // Null/undefined passthrough - if (obj === null || obj === undefined) { - return obj; - } - - // Primitive passthrough - if (typeof obj !== 'object') { - return obj; - } - - // Array handling - if (Array.isArray(obj)) { - return obj.map(item => sanitizeObject(item, config, depth + 1)); - } - - // Date handling - if (obj instanceof Date) { - return obj; - } - - // Object sanitization - return Object.entries(obj).reduce((acc, [key, value]) => { - // Check if field is sensitive (case-insensitive partial match) - const isSensitive = config.sensitiveFields.some( - sensitiveField => key.toLowerCase().includes(sensitiveField.toLowerCase()) - ); - - if (isSensitive) { - acc[key] = config.redactionText; - } else if (typeof value === 'object') { - acc[key] = sanitizeObject(value, config, depth + 1); - } else { - acc[key] = value; - } - - return acc; - }, {} as any); -} - -/** - * Detects if text contains PII using regex patterns - * - * @param text - Text to analyze - * @returns True if PII patterns detected - * - * @example - * ```typescript - * detectPII('Contact: user@example.com'); // true - * detectPII('Hello world'); // false - * ``` - */ -export function detectPII(text: string): boolean { - const patterns = { - email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/, - phone: /(\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/, - ssn: /\d{3}-\d{2}-\d{4}/, - creditCard: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/, - ipAddress: /\b(?:\d{1,3}\.){3}\d{1,3}\b/, - jwt: /eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/=]*/ - }; - - return Object.values(patterns).some(pattern => pattern.test(text)); -} - -/** - * Sanitizes a string by redacting detected PII - * - * @param text - Text to sanitize - * @param redactionText - Text to use for redaction - * @returns Sanitized text - * - * @example - * ```typescript - * sanitizeString('Email: user@example.com'); - * // Result: 'Email: [EMAIL_REDACTED]' - * ``` - */ -export function sanitizeString( - text: string, - redactionText = '[PII_REDACTED]' -): string { - let sanitized = text; - - // Redact emails - sanitized = sanitized.replace( - /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, - '[EMAIL_REDACTED]' - ); - - // Redact phone numbers - sanitized = sanitized.replace( - /(\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g, - '[PHONE_REDACTED]' - ); - - // Redact SSNs - sanitized = sanitized.replace( - /\d{3}-\d{2}-\d{4}/g, - '[SSN_REDACTED]' - ); - - // Redact credit cards - sanitized = sanitized.replace( - /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, - '[CARD_REDACTED]' - ); - - // Redact IP addresses - sanitized = sanitized.replace( - /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, - '[IP_REDACTED]' - ); - - // Redact JWTs - sanitized = sanitized.replace( - /eyJ[A-Za-z0-9-_]+\.eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_.+/=]*/g, - '[JWT_REDACTED]' - ); - - return sanitized; -} - -/** - * Creates a sanitization configuration for specific use cases - * - * @param additionalFields - Additional fields to consider sensitive - * @param redactionText - Custom redaction text - * @returns Sanitization configuration - */ -export function createSanitizationConfig( - additionalFields: string[] = [], - redactionText = '[REDACTED]' -): SanitizationConfig { - return { - ...DEFAULT_CONFIG, - sensitiveFields: [...DEFAULT_CONFIG.sensitiveFields, ...additionalFields], - redactionText - }; -} - -/** - * Sanitizes error objects for safe logging - * - * @param error - Error object to sanitize - * @returns Sanitized error information - */ -export function sanitizeError(error: any): any { - if (error instanceof Error) { - return { - name: error.name, - message: sanitizeString(error.message), - stack: process.env.NODE_ENV === 'production' ? undefined : error.stack - }; - } - - return sanitizeToolArgs(error); -} diff --git a/backend/shared/src/utils/__tests__/sanitization.test.ts b/backend/shared/src/utils/__tests__/sanitization.test.ts index c861191..5254599 100644 --- a/backend/shared/src/utils/__tests__/sanitization.test.ts +++ b/backend/shared/src/utils/__tests__/sanitization.test.ts @@ -11,8 +11,7 @@ import { redactPIIFromString, createSanitizer, sanitizeBatch, - DEFAULT_SENSITIVE_FIELDS, - DEFAULT_CONFIG + DEFAULT_SENSITIVE_FIELDS } from '../sanitization'; describe('PII Sanitization Utils', () => { diff --git a/backend/shared/src/utils/json-parser.ts b/backend/shared/src/utils/json-parser.ts index 1bf4c19..d4ae0fe 100644 --- a/backend/shared/src/utils/json-parser.ts +++ b/backend/shared/src/utils/json-parser.ts @@ -11,9 +11,14 @@ */ import { z, ZodSchema } from 'zod'; -import { Logger } from './logger'; -const logger = Logger.getInstance('json-parser'); +// Default logger implementation using console +const logger = { + info: (...args: any[]) => console.info('[json-parser]', ...args), + warn: (...args: any[]) => console.warn('[json-parser]', ...args), + error: (...args: any[]) => console.error('[json-parser]', ...args), + debug: (...args: any[]) => console.debug('[json-parser]', ...args), +}; export interface ParseOptions { /** Fallback value if parsing fails */ diff --git a/backend/shared/src/utils/redis-utils.ts b/backend/shared/src/utils/redis-utils.ts index 213f277..0ce668d 100644 --- a/backend/shared/src/utils/redis-utils.ts +++ b/backend/shared/src/utils/redis-utils.ts @@ -322,7 +322,7 @@ export class SafeRedisClient { async existsPattern(pattern: string): Promise { let cursor = '0'; - const [nextCursor, batch] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 10); + const [, batch] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 10); return batch.length > 0; } diff --git a/backend/shared/src/utils/sanitization.ts b/backend/shared/src/utils/sanitization.ts index 7bf265a..68e4a74 100644 --- a/backend/shared/src/utils/sanitization.ts +++ b/backend/shared/src/utils/sanitization.ts @@ -161,7 +161,7 @@ export function redactPIIFromString(text: string, redactionText: string = '[REDA let sanitized = text; // Apply each PII pattern - for (const [type, pattern] of Object.entries(PII_PATTERNS)) { + for (const pattern of Object.values(PII_PATTERNS)) { pattern.lastIndex = 0; sanitized = sanitized.replace(pattern, redactionText); } diff --git a/backend/tests/utils/__tests__/mock-data-generator.test.ts b/backend/tests/utils/__tests__/mock-data-generator.test.ts index b7d8f0a..7845d76 100644 --- a/backend/tests/utils/__tests__/mock-data-generator.test.ts +++ b/backend/tests/utils/__tests__/mock-data-generator.test.ts @@ -9,8 +9,7 @@ import { MockDataGenerator, createMockDataGenerator, mockDataGenerator, - SeededRandom, - LeaderboardEntry + SeededRandom } from '../mock-data-generator'; describe('MockDataGenerator', () => { diff --git a/scripts/check-comments.ts b/scripts/check-comments.ts index 6d653b5..2626a07 100644 --- a/scripts/check-comments.ts +++ b/scripts/check-comments.ts @@ -10,7 +10,6 @@ */ import * as fs from 'fs'; -import * as path from 'path'; import { glob } from 'glob'; interface CommentIssue { diff --git a/scripts/check-doc-links.ts b/scripts/check-doc-links.ts index 5a74fd2..e1043a5 100644 --- a/scripts/check-doc-links.ts +++ b/scripts/check-doc-links.ts @@ -77,7 +77,7 @@ async function checkMarkdownFiles(): Promise { MARKDOWN_LINK_REGEX.lastIndex = 0; while ((match = MARKDOWN_LINK_REGEX.exec(line)) !== null) { - const [, text, link] = match; + const [, , link] = match; if (!checkPathExists(file, link)) { brokenLinks.push({ From 0486f123ab1d2a6e130860d4650ec4bb492e9f0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 27 Nov 2025 19:04:15 +0000 Subject: [PATCH 3/3] fix(redis): make existsPattern iterate through all SCAN results Co-authored-by: clduab11 <185000089+clduab11@users.noreply.github.com> --- backend/shared/src/utils/redis-utils.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/shared/src/utils/redis-utils.ts b/backend/shared/src/utils/redis-utils.ts index 0ce668d..3ede9c1 100644 --- a/backend/shared/src/utils/redis-utils.ts +++ b/backend/shared/src/utils/redis-utils.ts @@ -322,9 +322,17 @@ export class SafeRedisClient { async existsPattern(pattern: string): Promise { let cursor = '0'; - const [, batch] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 10); + do { + const [nextCursor, batch] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100); + + if (batch.length > 0) { + return true; + } + + cursor = nextCursor; + } while (cursor !== '0'); - return batch.length > 0; + return false; } /**