/ src / agents / utils / claude-api.js
claude-api.js
  1  /**
  2   * Claude API Utility (DEPRECATED)
  3   *
  4   * ⚠️ DEPRECATED: This module is deprecated in favor of agent-claude-api.js
  5   * which uses the Anthropic API directly with Claude Max subscription.
  6   *
  7   * Migration guide:
  8   * - Use agent-claude-api.js instead
  9   * - All agents now use Anthropic API (not OpenRouter)
 10   * - OpenRouter is only used by pipeline stages (scoring, proposals, enrichment)
 11   *
 12   * @deprecated Use agent-claude-api.js instead
 13   */
 14  
 15  import Logger from '../../utils/logger.js';
 16  
 17  const logger = new Logger('ClaudeAPI');
 18  
 19  /**
 20   * Call Claude API via OpenRouter
 21   *
 22   * @param {Object} options - API call options
 23   * @param {string} options.prompt - The prompt to send to Claude
 24   * @param {string} [options.model='anthropic/claude-sonnet-4-6'] - Model to use
 25   * @param {number} [options.maxTokens=4000] - Maximum tokens in response
 26   * @param {number} [options.temperature=0.3] - Sampling temperature (0-1)
 27   * @param {string} [options.systemPrompt] - System prompt (optional)
 28   * @returns {Promise<string>} - Claude's response
 29   */
 30  export async function callClaude({
 31    prompt,
 32    model = 'anthropic/claude-sonnet-4-6',
 33    maxTokens = 4000,
 34    temperature = 0.3,
 35    systemPrompt = null,
 36  }) {
 37    if (!prompt) {
 38      throw new Error('Prompt is required');
 39    }
 40  
 41    if (!process.env.OPENROUTER_API_KEY) {
 42      throw new Error('OPENROUTER_API_KEY not configured');
 43    }
 44  
 45    const messages = [{ role: 'user', content: prompt }];
 46  
 47    const requestBody = {
 48      model,
 49      messages,
 50      max_tokens: maxTokens,
 51      temperature,
 52    };
 53  
 54    if (systemPrompt) {
 55      requestBody.system = systemPrompt;
 56    }
 57  
 58    try {
 59      const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
 60        method: 'POST',
 61        headers: {
 62          Authorization: `Bearer ${process.env.OPENROUTER_API_KEY}`,
 63          'Content-Type': 'application/json',
 64          'HTTP-Referer':
 65            process.env.OPENROUTER_REFERER || 'https://github.com/jasonpaulneu/333Method',
 66          'X-Title': process.env.OPENROUTER_TITLE || '333 Method Agent System',
 67        },
 68        body: JSON.stringify(requestBody),
 69      });
 70  
 71      if (!response.ok) {
 72        const errorText = await response.text();
 73        throw new Error(`OpenRouter API error: ${response.status} - ${errorText}`);
 74      }
 75  
 76      const data = await response.json();
 77  
 78      if (!data.choices || data.choices.length === 0) {
 79        throw new Error('No response from Claude API');
 80      }
 81  
 82      return data.choices[0].message.content;
 83    } catch (error) {
 84      logger.error('Claude API call failed', { error: error.message });
 85      throw error;
 86    }
 87  }
 88  
 89  /**
 90   * Analyze code for security vulnerabilities using Claude
 91   *
 92   * @param {string} code - Code to analyze
 93   * @param {string} [focusArea] - Specific security focus (sql_injection, xss, command_injection, secrets)
 94   * @param {string} [fileName] - File name for context
 95   * @returns {Promise<Object>} - Analysis result with findings
 96   */
 97  export async function analyzeCodeSecurity(code, focusArea = null, fileName = null) {
 98    const focusInstruction = focusArea
 99      ? `Focus specifically on: ${focusArea.replace(/_/g, ' ')}`
100      : 'Check all security aspects';
101  
102    const fileContext = fileName ? `File: ${fileName}\n\n` : '';
103  
104    const prompt = `Analyze this code for security vulnerabilities.
105  
106  ${focusInstruction}
107  
108  ${fileContext}Code:
109  \`\`\`
110  ${code}
111  \`\`\`
112  
113  Provide analysis in JSON format:
114  {
115    "findings": [
116      {
117        "type": "vulnerability_type",
118        "severity": "critical|high|medium|low",
119        "line": line_number,
120        "description": "detailed description",
121        "recommendation": "how to fix",
122        "cwe_id": "CWE-XXX (if applicable)"
123      }
124    ],
125    "summary": "overall security assessment"
126  }`;
127  
128    const systemPrompt = `You are a security expert analyzing code for vulnerabilities.
129  Be thorough but practical. Focus on real exploitable issues, not theoretical concerns.
130  Classify severity accurately:
131  - critical: Direct exploitable vulnerability with high impact
132  - high: Exploitable with some conditions, or severe impact
133  - medium: Security weakness requiring specific conditions
134  - low: Minor issue or best practice violation`;
135  
136    const response = await callClaude({
137      prompt,
138      systemPrompt,
139      temperature: 0.2,
140      maxTokens: 3000,
141    });
142  
143    try {
144      // Extract JSON from response (handle markdown code blocks)
145      const jsonMatch =
146        response.match(/```json\s*([\s\S]*?)\s*```/) || response.match(/```\s*([\s\S]*?)\s*```/);
147      const jsonStr = jsonMatch ? jsonMatch[1] : response;
148  
149      return JSON.parse(jsonStr);
150    } catch (error) {
151      logger.error('Failed to parse Claude security analysis', { error: error.message, response });
152      throw new Error(`Failed to parse security analysis: ${error.message}`);
153    }
154  }
155  
156  /**
157   * Generate a secure fix for a vulnerability using Claude
158   *
159   * @param {Object} options - Fix generation options
160   * @param {string} options.code - Original code with vulnerability
161   * @param {Object} options.finding - Security finding object
162   * @param {string} [options.fileName] - File name for context
163   * @returns {Promise<Object>} - Fix result with old_string, new_string, explanation
164   */
165  export async function generateSecureFix({ code, finding, fileName = null }) {
166    const fileContext = fileName ? `File: ${fileName}\n\n` : '';
167  
168    const prompt = `Fix this security vulnerability in the code.
169  
170  Vulnerability:
171  - Type: ${finding.type}
172  - Severity: ${finding.severity}
173  - Line: ${finding.line || 'unknown'}
174  - Description: ${finding.description}
175  - Recommendation: ${finding.recommendation}
176  
177  ${fileContext}Original Code:
178  \`\`\`
179  ${code}
180  \`\`\`
181  
182  Provide the fix in JSON format:
183  {
184    "old_string": "exact string to replace (must match code exactly)",
185    "new_string": "secure replacement code",
186    "explanation": "why this fix is secure",
187    "testing_notes": "how to test the fix"
188  }
189  
190  IMPORTANT:
191  - old_string must match the vulnerable code EXACTLY (including whitespace, indentation)
192  - new_string should maintain the same functionality but be secure
193  - Preserve code style and formatting
194  - Keep the fix minimal - only change what's necessary`;
195  
196    const systemPrompt = `You are a security expert who fixes code vulnerabilities.
197  Provide practical, secure fixes that maintain functionality.
198  Ensure old_string matches the original code EXACTLY.
199  Follow secure coding best practices for the language.`;
200  
201    const response = await callClaude({
202      prompt,
203      systemPrompt,
204      temperature: 0.2,
205      maxTokens: 2000,
206    });
207  
208    try {
209      // Extract JSON from response
210      const jsonMatch =
211        response.match(/```json\s*([\s\S]*?)\s*```/) || response.match(/```\s*([\s\S]*?)\s*```/);
212      const jsonStr = jsonMatch ? jsonMatch[1] : response;
213  
214      const fix = JSON.parse(jsonStr);
215  
216      // Validate fix has required fields
217      if (!fix.old_string || !fix.new_string) {
218        throw new Error('Fix missing old_string or new_string');
219      }
220  
221      return fix;
222    } catch (error) {
223      logger.error('Failed to parse Claude fix generation', { error: error.message, response });
224      throw new Error(`Failed to generate secure fix: ${error.message}`);
225    }
226  }
227  
228  /**
229   * Perform STRIDE threat modeling on a system component
230   *
231   * @param {Object} options - Threat modeling options
232   * @param {string} options.component - Component description or code
233   * @param {string} [options.componentType] - Type (api, database, auth, file_upload, etc)
234   * @param {string} [options.dataFlow] - Description of data flow
235   * @returns {Promise<Object>} - Threat model with STRIDE analysis and DREAD scores
236   */
237  export async function performThreatModeling({
238    component,
239    componentType = 'general',
240    dataFlow = null,
241  }) {
242    const dataFlowContext = dataFlow ? `\n\nData Flow:\n${dataFlow}` : '';
243  
244    const prompt = `Perform STRIDE threat modeling on this component.
245  
246  Component Type: ${componentType}
247  ${dataFlowContext}
248  
249  Component:
250  \`\`\`
251  ${component}
252  \`\`\`
253  
254  Analyze using STRIDE methodology:
255  - Spoofing: Can identity be faked?
256  - Tampering: Can data be modified?
257  - Repudiation: Can actions be denied?
258  - Information Disclosure: Can sensitive data leak?
259  - Denial of Service: Can availability be disrupted?
260  - Elevation of Privilege: Can permissions be bypassed?
261  
262  For each threat found, calculate DREAD score:
263  - Damage potential (1-10)
264  - Reproducibility (1-10)
265  - Exploitability (1-10)
266  - Affected users (1-10)
267  - Discoverability (1-10)
268  
269  Provide analysis in JSON format:
270  {
271    "threats": [
272      {
273        "stride_category": "Spoofing|Tampering|Repudiation|InformationDisclosure|DoS|ElevationOfPrivilege",
274        "title": "short threat title",
275        "description": "detailed threat description",
276        "attack_scenario": "how attacker would exploit",
277        "dread": {
278          "damage": 1-10,
279          "reproducibility": 1-10,
280          "exploitability": 1-10,
281          "affected_users": 1-10,
282          "discoverability": 1-10,
283          "total": sum,
284          "average": average
285        },
286        "risk_level": "critical|high|medium|low",
287        "mitigation": "how to mitigate the threat",
288        "cwe_id": "CWE-XXX (if applicable)"
289      }
290    ],
291    "summary": "overall threat assessment",
292    "priority_threats": ["list of high/critical threats to fix immediately"]
293  }`;
294  
295    const systemPrompt = `You are a security architect performing threat modeling.
296  Use STRIDE methodology systematically.
297  Calculate DREAD scores objectively:
298  - Damage: 10=complete compromise, 1=minimal impact
299  - Reproducibility: 10=always works, 1=difficult/unreliable
300  - Exploitability: 10=no skills needed, 1=expert only
301  - Affected users: 10=all users, 1=few users
302  - Discoverability: 10=obvious/documented, 1=obscure
303  
304  Risk levels based on DREAD average:
305  - critical: 8.5-10
306  - high: 7.0-8.4
307  - medium: 4.0-6.9
308  - low: 1.0-3.9`;
309  
310    const response = await callClaude({
311      prompt,
312      systemPrompt,
313      temperature: 0.3,
314      maxTokens: 4000,
315    });
316  
317    try {
318      // Extract JSON from response
319      const jsonMatch =
320        response.match(/```json\s*([\s\S]*?)\s*```/) || response.match(/```\s*([\s\S]*?)\s*```/);
321      const jsonStr = jsonMatch ? jsonMatch[1] : response;
322  
323      return JSON.parse(jsonStr);
324    } catch (error) {
325      logger.error('Failed to parse Claude threat model', { error: error.message, response });
326      throw new Error(`Failed to perform threat modeling: ${error.message}`);
327    }
328  }