├── gptbestp1.png ├── .github └── FUNDING.yml └── README.md /gptbestp1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VolkanSah/AI-API-Security-Best-Practices/HEAD/gptbestp1.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [volkansah] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: volkansah 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛡️ AI API Security (Best Practices) 2 | 3 | **Universal Security Guide for OpenAI, Anthropic Claude, Google Gemini & Other LLM APIs** 4 | 5 | [![PHP](https://img.shields.io/badge/PHP-8.x-777BB4?logo=php)](https://php.net) 6 | [![Python](https://img.shields.io/badge/Python-3.x-3776AB?logo=python)](https://python.org) 7 | [![Node.js](https://img.shields.io/badge/Node.js-18.x-339933?logo=node.js)](https://nodejs.org) 8 | [![Security](https://img.shields.io/badge/Security-Critical-red)]() 9 | 10 | > **2025/26 Update**: Expanded for all major AI providers with a focus on WordPress & TYPO3 integrations 11 | 12 | --- 13 | 14 | ### Table of Contents 15 | 16 | - [Introduction](#-introduction) 17 | - [Critical Security Risks](#-critical-security-risks) 18 | - [Common Mistakes](#common-mistakes) 19 | - [OWASP Top 10 for LLMs](#owasp-top-10-for-llms-2025) 20 | - [API Key Management](#-api-key-management) 21 | - [Never Do This](#️-never) 22 | - [Environment Variables](#-correct-environment-variables) 23 | - [Additional Key Security](#-additional-key-security) 24 | - [Framework-Specific Guides](#️-framework-specific-guides) 25 | - [WordPress Integration](#wordpress-integration) 26 | - [TYPO3 Extension](#typo3-extension) 27 | - [Code Examples: Multi-Provider Support](#-code-examples-multi-provider-support) 28 | - [Universal PHP API Client](#universal-php-api-client) 29 | - [Python Async Implementation](#python-async-implementation) 30 | - [Security Checklist](#️-security-checklist) 31 | - [Provider-Specific Documentation](#provider-specific-documentation) 32 | - [Additional Security Resources](#-additional-security-resources) 33 | - [Support & Contributions](#-support--contributions) 34 | - [License](#-license) 35 | - [Disclaimer](#️-disclaimer) 36 | 37 | --- 38 | 39 | ## Introduction 40 | 41 | While the AI hype continues, we are witnessing catastrophic security failures in LLM API integrations. These best practices cover **all major providers** and are specifically optimized for production environments. 42 | 43 | ### Supported Providers 44 | 45 | - **OpenAI** 46 | - **Anthropic** 47 | - **Google** 48 | - **Meta** 49 | - **Mistral AI** 50 | - **Other OpenAI-compatible APIs** 51 | 52 | --- 53 | 54 | ### 🔥 Critical Security Risks 55 | 56 | ### Common Mistakes 57 | 58 | #### 1. API Keys in Client-Side Code 59 | 60 | - ❌ Keys embedded in JavaScript/HTML 61 | - ❌ Keys committed to Git repositories 62 | - ❌ Keys visible in Browser Console/Network tab 63 | 64 | #### 2. Missing Input Validation 65 | 66 | - ❌ Direct pass-through of user input to API 67 | - ❌ No sanitization or filtering 68 | - ❌ No rate limiting implementation 69 | 70 | #### 3. Insecure Output Handling 71 | 72 | - ❌ LLM output directly inserted into database/code 73 | - ❌ No XSS protection 74 | - ❌ No SQL injection prevention 75 | 76 | ### OWASP Top 10 for LLMs (2025) 77 | 78 | | Rank | Risk | Description | 79 | |------|------|-------------| 80 | | **LLM01** | **Prompt Injection** | Manipulation of the LLM through crafted inputs | 81 | | **LLM02** | **Improper Output Handling** | Unfiltered LLM outputs leading to XSS/RCE | 82 | | **LLM03** | **Data & Model Poisoning** | Manipulation of training data | 83 | | **LLM04** | **Unbounded Consumption** | DoS through resource exhaustion | 84 | | **LLM05** | **Supply Chain Vulnerabilities** | Compromised dependencies | 85 | | **LLM06** | **Sensitive Information Disclosure** | Data leaks via LLM outputs | 86 | | **LLM07** | **System Prompt Leakage** | Exposure of system prompts & secrets | 87 | | **LLM08** | **Vector & Embedding Weaknesses** | RAG/Embedding security issues | 88 | | **LLM09** | **Misinformation** | Over-reliance on LLM outputs | 89 | | **LLM10** | **Excessive Agency** | Uncontrolled LLM autonomy | 90 | 91 | --- 92 | 93 | ### API Key Management 94 | 95 | #### ⚠️ NEVER 96 | 97 | ```php 98 | // ❌ NEVER DO THIS! 99 | $api_key = "sk-proj-abc123..."; 100 | ``` 101 | 102 | ```javascript 103 | // ❌ NEVER DO THIS! 104 | const API_KEY = "sk-proj-abc123..."; 105 | ``` 106 | 107 | ```python 108 | # ❌ NEVER DO THIS! 109 | api_key = "sk-proj-abc123..." 110 | ``` 111 | 112 | #### ↪️ CORRECT: Environment Variables 113 | 114 | #### PHP with `vlucas/phpdotenv` 115 | 116 | ```bash 117 | composer require vlucas/phpdotenv 118 | ``` 119 | 120 | ```env 121 | # .env (DO NOT COMMIT TO GIT!) 122 | OPENAI_API_KEY=sk-proj-xxx 123 | ANTHROPIC_API_KEY=sk-ant-xxx 124 | GOOGLE_API_KEY=AIzaSyxxx 125 | ``` 126 | 127 | ```php 128 | safeLoad(); 135 | 136 | $openai_key = $_ENV['OPENAI_API_KEY']; 137 | $claude_key = $_ENV['ANTHROPIC_API_KEY']; 138 | $gemini_key = $_ENV['GOOGLE_API_KEY']; 139 | ``` 140 | 141 | #### Python with `python-dotenv` 142 | 143 | ```bash 144 | pip install python-dotenv 145 | ``` 146 | 147 | ```env 148 | # .env (DO NOT COMMIT TO GIT!) 149 | OPENAI_API_KEY=sk-proj-xxx 150 | ANTHROPIC_API_KEY=sk-ant-xxx 151 | ``` 152 | 153 | ```python 154 | # app.py 155 | import os 156 | from dotenv import load_dotenv 157 | 158 | load_dotenv() 159 | 160 | openai_key = os.getenv('OPENAI_API_KEY') 161 | claude_key = os.getenv('ANTHROPIC_API_KEY') 162 | ``` 163 | 164 | #### Node.js with `dotenv` 165 | 166 | ```bash 167 | npm install dotenv 168 | ``` 169 | 170 | ```env 171 | # .env (DO NOT COMMIT TO GIT!) 172 | OPENAI_API_KEY=sk-proj-xxx 173 | ANTHROPIC_API_KEY=sk-ant-xxx 174 | ``` 175 | 176 | ```javascript 177 | // app.js 178 | require('dotenv').config(); 179 | 180 | const openaiKey = process.env.OPENAI_API_KEY; 181 | const claudeKey = process.env.ANTHROPIC_API_KEY; 182 | ``` 183 | 184 | ### 🔒 Additional Key Security for your repos 185 | 186 | ```gitignore 187 | # .gitignore 188 | .env 189 | .env.* 190 | !.env.example 191 | *.key 192 | secrets/ 193 | credentials/ 194 | ``` 195 | 196 | **Always create a `.env.example` file:** 197 | 198 | ```env 199 | # .env.example 200 | OPENAI_API_KEY=your_openai_key_here 201 | ANTHROPIC_API_KEY=your_anthropic_key_here 202 | GOOGLE_API_KEY=your_google_key_here 203 | ``` 204 | 205 | --- 206 | 207 | ### Framework-Specific Guides 208 | 209 | ### WordPress Integration 210 | 211 | ```php 212 | 'Insufficient permissions']); 241 | return; 242 | } 243 | 244 | // Input sanitization 245 | $user_input = sanitize_textarea_field($_POST['prompt']); 246 | 247 | // Validate input length 248 | if (strlen($user_input) > 4000) { 249 | wp_send_json_error(['message' => 'Prompt too long']); 250 | return; 251 | } 252 | 253 | // Rate limiting via transients (1 request per minute per user) 254 | $user_id = get_current_user_id(); 255 | $rate_key = "ai_rate_$user_id"; 256 | 257 | if (get_transient($rate_key)) { 258 | wp_send_json_error(['message' => 'Rate limit exceeded. Please wait.']); 259 | return; 260 | } 261 | 262 | set_transient($rate_key, true, 60); // 60 seconds 263 | 264 | // Make API call 265 | $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [ 266 | 'timeout' => 30, 267 | 'headers' => [ 268 | 'Authorization' => 'Bearer ' . OPENAI_API_KEY, 269 | 'Content-Type' => 'application/json' 270 | ], 271 | 'body' => json_encode([ 272 | 'model' => 'gpt-4o', 273 | 'messages' => [ 274 | ['role' => 'system', 'content' => 'You are a helpful assistant.'], 275 | ['role' => 'user', 'content' => $user_input] 276 | ], 277 | 'max_tokens' => 1000 278 | ]) 279 | ]); 280 | 281 | // Error handling 282 | if (is_wp_error($response)) { 283 | error_log('AI API Error: ' . $response->get_error_message()); 284 | wp_send_json_error(['message' => 'API request failed']); 285 | return; 286 | } 287 | 288 | $http_code = wp_remote_retrieve_response_code($response); 289 | if ($http_code !== 200) { 290 | error_log('AI API HTTP Error: ' . $http_code); 291 | wp_send_json_error(['message' => 'API returned error']); 292 | return; 293 | } 294 | 295 | $data = json_decode(wp_remote_retrieve_body($response), true); 296 | 297 | // Output sanitization (allow safe HTML tags) 298 | $ai_response = wp_kses_post($data['choices'][0]['message']['content']); 299 | 300 | wp_send_json_success(['response' => $ai_response]); 301 | } 302 | 303 | /** 304 | * Enqueue scripts with nonce 305 | */ 306 | add_action('wp_enqueue_scripts', 'enqueue_ai_scripts'); 307 | 308 | function enqueue_ai_scripts() { 309 | wp_enqueue_script('ai-handler', plugins_url('js/ai-handler.js', __FILE__), ['jquery'], '1.0.0', true); 310 | 311 | wp_localize_script('ai-handler', 'aiConfig', [ 312 | 'ajaxUrl' => admin_url('admin-ajax.php'), 313 | 'nonce' => wp_create_nonce('ai_nonce') 314 | ]); 315 | } 316 | ``` 317 | 318 | **Frontend JavaScript (ai-handler.js):** 319 | 320 | ```javascript 321 | jQuery(document).ready(function($) { 322 | $('#ai-submit').on('click', function(e) { 323 | e.preventDefault(); 324 | 325 | const prompt = $('#ai-prompt').val(); 326 | const $button = $(this); 327 | const $result = $('#ai-result'); 328 | 329 | $button.prop('disabled', true).text('Generating...'); 330 | 331 | $.ajax({ 332 | url: aiConfig.ajaxUrl, 333 | type: 'POST', 334 | data: { 335 | action: 'ai_request', 336 | nonce: aiConfig.nonce, 337 | prompt: prompt 338 | }, 339 | success: function(response) { 340 | if (response.success) { 341 | $result.html(response.data.response); 342 | } else { 343 | $result.html('

' + response.data.message + '

'); 344 | } 345 | }, 346 | error: function() { 347 | $result.html('

Request failed. Please try again.

'); 348 | }, 349 | complete: function() { 350 | $button.prop('disabled', false).text('Generate'); 351 | } 352 | }); 353 | }); 354 | }); 355 | ``` 356 | 357 | #### TYPO3 Extension 358 | 359 | ```php 360 | apiKey = $extConfig->get('your_extension', 'openaiApiKey') 395 | ?: getenv('OPENAI_API_KEY'); 396 | 397 | if (empty($this->apiKey)) { 398 | throw new \RuntimeException('OpenAI API key not configured'); 399 | } 400 | 401 | $this->client = new Client([ 402 | 'timeout' => 30, 403 | 'verify' => true 404 | ]); 405 | } 406 | 407 | /** 408 | * Generate content using OpenAI API 409 | * 410 | * @param string $prompt User prompt 411 | * @return string Generated content 412 | * @throws \InvalidArgumentException 413 | * @throws \RuntimeException 414 | */ 415 | public function generateContent(string $prompt): string 416 | { 417 | // Input validation 418 | if (strlen($prompt) > 4000) { 419 | throw new \InvalidArgumentException('Prompt exceeds maximum length'); 420 | } 421 | 422 | if (empty(trim($prompt))) { 423 | throw new \InvalidArgumentException('Prompt cannot be empty'); 424 | } 425 | 426 | // Rate limiting via cache (1 request per minute per backend user) 427 | $userId = $GLOBALS['BE_USER']->user['uid'] ?? 0; 428 | $cacheKey = 'ai_rate_' . $userId; 429 | $cache = GeneralUtility::makeInstance(CacheManager::class)->getCache('runtime'); 430 | 431 | if ($cache->has($cacheKey)) { 432 | throw new \RuntimeException('Rate limit exceeded. Please wait before making another request.'); 433 | } 434 | 435 | $cache->set($cacheKey, true, [], 60); 436 | 437 | try { 438 | // Make API call 439 | $response = $this->client->post('https://api.openai.com/v1/chat/completions', [ 440 | 'headers' => [ 441 | 'Authorization' => 'Bearer ' . $this->apiKey, 442 | 'Content-Type' => 'application/json' 443 | ], 444 | 'json' => [ 445 | 'model' => 'gpt-4o', 446 | 'messages' => [ 447 | [ 448 | 'role' => 'system', 449 | 'content' => 'Generate SEO-optimized, professional content.' 450 | ], 451 | [ 452 | 'role' => 'user', 453 | 'content' => $prompt 454 | ] 455 | ], 456 | 'max_tokens' => 1500, 457 | 'temperature' => 0.7 458 | ] 459 | ]); 460 | 461 | $data = json_decode($response->getBody()->getContents(), true); 462 | 463 | if (!isset($data['choices'][0]['message']['content'])) { 464 | throw new \RuntimeException('Invalid API response'); 465 | } 466 | 467 | // Output sanitization 468 | $content = $data['choices'][0]['message']['content']; 469 | return htmlspecialchars($content, ENT_QUOTES, 'UTF-8'); 470 | 471 | } catch (GuzzleException $e) { 472 | // Log error without exposing API key 473 | GeneralUtility::sysLog( 474 | 'AI API request failed: ' . $e->getMessage(), 475 | 'your_extension', 476 | GeneralUtility::SYSLOG_SEVERITY_ERROR 477 | ); 478 | 479 | throw new \RuntimeException('AI service temporarily unavailable'); 480 | } 481 | } 482 | } 483 | ``` 484 | 485 | ```php 486 | aiService = $aiService; 503 | } 504 | 505 | public function generateAction(): ResponseInterface 506 | { 507 | $prompt = $this->request->getArgument('prompt'); 508 | 509 | try { 510 | $content = $this->aiService->generateContent($prompt); 511 | $this->view->assign('content', $content); 512 | $this->view->assign('success', true); 513 | } catch (\Exception $e) { 514 | $this->view->assign('error', $e->getMessage()); 515 | $this->view->assign('success', false); 516 | } 517 | 518 | return $this->htmlResponse(); 519 | } 520 | } 521 | ``` 522 | 523 | --- 524 | 525 | ### Code Examples: Multi-Provider Support 526 | 527 | ### Universal PHP API Client 528 | 529 | ```php 530 | validateConfig($config); 543 | $this->config = $config; 544 | } 545 | 546 | /** 547 | * Send chat completion request to specified provider 548 | * 549 | * @param string $provider Provider name (openai, claude, gemini) 550 | * @param string $prompt User prompt 551 | * @param array $options Additional options 552 | * @return string AI response 553 | * @throws InvalidArgumentException 554 | * @throws RuntimeException 555 | */ 556 | public function chat(string $provider, string $prompt, array $options = []): string 557 | { 558 | $handlers = [ 559 | 'openai' => fn() => $this->callOpenAI($prompt, $options), 560 | 'claude' => fn() => $this->callClaude($prompt, $options), 561 | 'gemini' => fn() => $this->callGemini($prompt, $options), 562 | ]; 563 | 564 | if (!isset($handlers[$provider])) { 565 | throw new \InvalidArgumentException("Unknown provider: $provider"); 566 | } 567 | 568 | return $handlers[$provider](); 569 | } 570 | 571 | /** 572 | * OpenAI API implementation 573 | */ 574 | private function callOpenAI(string $prompt, array $options): string 575 | { 576 | $ch = curl_init('https://api.openai.com/v1/chat/completions'); 577 | 578 | $payload = [ 579 | 'model' => $options['model'] ?? 'gpt-4o', 580 | 'messages' => [ 581 | ['role' => 'user', 'content' => $prompt] 582 | ], 583 | 'max_tokens' => $options['max_tokens'] ?? 1000, 584 | 'temperature' => $options['temperature'] ?? 0.7 585 | ]; 586 | 587 | curl_setopt_array($ch, [ 588 | CURLOPT_POST => true, 589 | CURLOPT_RETURNTRANSFER => true, 590 | CURLOPT_TIMEOUT => self::TIMEOUT, 591 | CURLOPT_HTTPHEADER => [ 592 | 'Authorization: Bearer ' . $this->config['openai_key'], 593 | 'Content-Type: application/json' 594 | ], 595 | CURLOPT_POSTFIELDS => json_encode($payload) 596 | ]); 597 | 598 | $response = curl_exec($ch); 599 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 600 | $error = curl_error($ch); 601 | curl_close($ch); 602 | 603 | if ($error) { 604 | throw new \RuntimeException("OpenAI request failed: $error"); 605 | } 606 | 607 | if ($httpCode !== 200) { 608 | throw new \RuntimeException("OpenAI API error: HTTP $httpCode"); 609 | } 610 | 611 | $data = json_decode($response, true); 612 | 613 | if (!isset($data['choices'][0]['message']['content'])) { 614 | throw new \RuntimeException("Invalid OpenAI response format"); 615 | } 616 | 617 | return $data['choices'][0]['message']['content']; 618 | } 619 | 620 | /** 621 | * Anthropic Claude API implementation 622 | */ 623 | private function callClaude(string $prompt, array $options): string 624 | { 625 | $ch = curl_init('https://api.anthropic.com/v1/messages'); 626 | 627 | $payload = [ 628 | 'model' => $options['model'] ?? 'claude-3-5-sonnet-20241022', 629 | 'max_tokens' => $options['max_tokens'] ?? 1024, 630 | 'messages' => [ 631 | ['role' => 'user', 'content' => $prompt] 632 | ] 633 | ]; 634 | 635 | curl_setopt_array($ch, [ 636 | CURLOPT_POST => true, 637 | CURLOPT_RETURNTRANSFER => true, 638 | CURLOPT_TIMEOUT => self::TIMEOUT, 639 | CURLOPT_HTTPHEADER => [ 640 | 'x-api-key: ' . $this->config['claude_key'], 641 | 'anthropic-version: 2023-06-01', 642 | 'Content-Type: application/json' 643 | ], 644 | CURLOPT_POSTFIELDS => json_encode($payload) 645 | ]); 646 | 647 | $response = curl_exec($ch); 648 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 649 | $error = curl_error($ch); 650 | curl_close($ch); 651 | 652 | if ($error) { 653 | throw new \RuntimeException("Claude request failed: $error"); 654 | } 655 | 656 | if ($httpCode !== 200) { 657 | throw new \RuntimeException("Claude API error: HTTP $httpCode"); 658 | } 659 | 660 | $data = json_decode($response, true); 661 | 662 | if (!isset($data['content'][0]['text'])) { 663 | throw new \RuntimeException("Invalid Claude response format"); 664 | } 665 | 666 | return $data['content'][0]['text']; 667 | } 668 | 669 | /** 670 | * Google Gemini API implementation 671 | */ 672 | private function callGemini(string $prompt, array $options): string 673 | { 674 | $model = $options['model'] ?? 'gemini-1.5-pro'; 675 | $url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key=" . $this->config['gemini_key']; 676 | 677 | $ch = curl_init($url); 678 | 679 | $payload = [ 680 | 'contents' => [ 681 | ['parts' => [['text' => $prompt]]] 682 | ], 683 | 'generationConfig' => [ 684 | 'temperature' => $options['temperature'] ?? 0.7, 685 | 'maxOutputTokens' => $options['max_tokens'] ?? 1000 686 | ] 687 | ]; 688 | 689 | curl_setopt_array($ch, [ 690 | CURLOPT_POST => true, 691 | CURLOPT_RETURNTRANSFER => true, 692 | CURLOPT_TIMEOUT => self::TIMEOUT, 693 | CURLOPT_HTTPHEADER => ['Content-Type: application/json'], 694 | CURLOPT_POSTFIELDS => json_encode($payload) 695 | ]); 696 | 697 | $response = curl_exec($ch); 698 | $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 699 | $error = curl_error($ch); 700 | curl_close($ch); 701 | 702 | if ($error) { 703 | throw new \RuntimeException("Gemini request failed: $error"); 704 | } 705 | 706 | if ($httpCode !== 200) { 707 | throw new \RuntimeException("Gemini API error: HTTP $httpCode"); 708 | } 709 | 710 | $data = json_decode($response, true); 711 | 712 | if (!isset($data['candidates'][0]['content']['parts'][0]['text'])) { 713 | throw new \RuntimeException("Invalid Gemini response format"); 714 | } 715 | 716 | return $data['candidates'][0]['content']['parts'][0]['text']; 717 | } 718 | 719 | /** 720 | * Validate configuration 721 | */ 722 | private function validateConfig(array $config): void 723 | { 724 | $required = ['openai_key', 'claude_key', 'gemini_key']; 725 | 726 | foreach ($required as $key) { 727 | if (empty($config[$key])) { 728 | throw new \InvalidArgumentException("Missing required config: $key"); 729 | } 730 | } 731 | } 732 | } 733 | 734 | // Usage example 735 | try { 736 | $client = new UniversalAIClient([ 737 | 'openai_key' => getenv('OPENAI_API_KEY'), 738 | 'claude_key' => getenv('ANTHROPIC_API_KEY'), 739 | 'gemini_key' => getenv('GOOGLE_API_KEY') 740 | ]); 741 | 742 | // OpenAI request 743 | $response = $client->chat('openai', 'Explain quantum computing in simple terms'); 744 | echo "OpenAI: " . $response . "\n\n"; 745 | 746 | // Claude request 747 | $response = $client->chat('claude', 'Write a haiku about security', [ 748 | 'model' => 'claude-3-5-sonnet-20241022', 749 | 'max_tokens' => 100 750 | ]); 751 | echo "Claude: " . $response . "\n\n"; 752 | 753 | // Gemini request 754 | $response = $client->chat('gemini', 'What are best practices for API security?'); 755 | echo "Gemini: " . $response . "\n"; 756 | 757 | } catch (Exception $e) { 758 | error_log("AI Client Error: " . $e->getMessage()); 759 | echo "An error occurred. Please try again later."; 760 | } 761 | ``` 762 | 763 | ### Python Async Implementation 764 | 765 | ```python 766 | """ 767 | Universal AI API Client - Async Implementation 768 | Supports OpenAI, Anthropic Claude, and Google Gemini 769 | """ 770 | import asyncio 771 | import aiohttp 772 | import os 773 | from typing import Dict, Any, Optional 774 | from dataclasses import dataclass 775 | 776 | @dataclass 777 | class ProviderConfig: 778 | """Configuration for AI provider""" 779 | url: str 780 | key: str 781 | header: str 782 | prefix: str = '' 783 | version_header: Optional[str] = None 784 | 785 | class UniversalAIClient: 786 | """Universal async client for multiple AI providers""" 787 | 788 | def __init__(self): 789 | self.config = { 790 | 'openai': ProviderConfig( 791 | url='https://api.openai.com/v1/chat/completions', 792 | key=os.getenv('OPENAI_API_KEY', ''), 793 | header='Authorization', 794 | prefix='Bearer ' 795 | ), 796 | 'claude': ProviderConfig( 797 | url='https://api.anthropic.com/v1/messages', 798 | key=os.getenv('ANTHROPIC_API_KEY', ''), 799 | header='x-api-key', 800 | prefix='', 801 | version_header='2023-06-01' 802 | ), 803 | 'gemini': ProviderConfig( 804 | url='https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent', 805 | key=os.getenv('GOOGLE_API_KEY', ''), 806 | header='', 807 | prefix='' 808 | ) 809 | } 810 | 811 | self._validate_config() 812 | 813 | def _validate_config(self) -> None: 814 | """Validate that all API keys are configured""" 815 | for provider, config in self.config.items(): 816 | if not config.key: 817 | raise ValueError(f"Missing API key for {provider}") 818 | 819 | async def chat( 820 | self, 821 | provider: str, 822 | prompt: str, 823 | model: Optional[str] = None, 824 | max_tokens: int = 1000, 825 | temperature: float = 0.7 826 | ) -> str: 827 | """ 828 | Send chat completion request to specified provider 829 | 830 | Args: 831 | provider: Provider name (openai, claude, gemini) 832 | prompt: User prompt 833 | model: Optional model override 834 | max_tokens: Maximum tokens in response 835 | temperature: Creativity parameter (0-1) 836 | 837 | Returns: 838 | AI response text 839 | 840 | Raises: 841 | ValueError: If provider is unknown 842 | RuntimeError: If API request fails 843 | """ 844 | if provider not in self.config: 845 | raise ValueError(f"Unknown provider: {provider}") 846 | 847 | cfg = self.config[provider] 848 | 849 | async with aiohttp.ClientSession() as session: 850 | headers = self._build_headers(provider, cfg) 851 | payload = self._build_payload( 852 | provider, prompt, model, max_tokens, temperature 853 | ) 854 | 855 | # Add API key to URL for Gemini 856 | url = cfg.url 857 | if provider == 'gemini': 858 | url = f"{url}?key={cfg.key}" 859 | 860 | async with session.post(url, headers=headers, json=payload, timeout=30) as resp: 861 | if resp.status != 200: 862 | error_text = await resp.text() 863 | raise RuntimeError( 864 | f"{provider} API error: HTTP {resp.status} - {error_text}" 865 | ) 866 | 867 | data = await resp.json() 868 | return self._extract_response(provider, data) 869 | 870 | def _build_headers(self, provider: str, cfg: ProviderConfig) -> Dict[str, str]: 871 | """Build request headers for provider""" 872 | headers = {'Content-Type': 'application/json'} 873 | 874 | if provider == 'openai': 875 | headers[cfg.header] = f"{cfg.prefix}{cfg.key}" 876 | elif provider == 'claude': 877 | headers[cfg.header] = cfg.key 878 | headers['anthropic-version'] = cfg.version_header 879 | # Gemini uses API key in URL, not headers 880 | 881 | return headers 882 | 883 | def _build_payload( 884 | self, 885 | provider: str, 886 | prompt: str, 887 | model: Optional[str], 888 | max_tokens: int, 889 | temperature: float 890 | ) -> Dict[str, Any]: 891 | """Build request payload for provider""" 892 | if provider == 'openai': 893 | return { 894 | 'model': model or 'gpt-4o', 895 | 'messages': [{'role': 'user', 'content': prompt}], 896 | 'max_tokens': max_tokens, 897 | 'temperature': temperature 898 | } 899 | 900 | elif provider == 'claude': 901 | return { 902 | 'model': model or 'claude-3-5-sonnet-20241022', 903 | 'max_tokens': max_tokens, 904 | 'messages': [{'role': 'user', 'content': prompt}], 905 | 'temperature': temperature 906 | } 907 | 908 | elif provider == 'gemini': 909 | return { 910 | 'contents': [{'parts': [{'text': prompt}]}], 911 | 'generationConfig': { 912 | 'temperature': temperature, 913 | 'maxOutputTokens': max_tokens 914 | } 915 | } 916 | 917 | raise ValueError(f"Unknown provider: {provider}") 918 | 919 | def _extract_response(self, provider: str, data: Dict[str, Any]) -> str: 920 | """Extract response text from provider response""" 921 | try: 922 | if provider == 'openai': 923 | return data['choices'][0]['message']['content'] 924 | elif provider == 'claude': 925 | return data['content'][0]['text'] 926 | elif provider == 'gemini': 927 | return data['candidates'][0]['content']['parts'][0]['text'] 928 | except (KeyError, IndexError) as e: 929 | raise RuntimeError(f"Invalid {provider} response format: {e}") 930 | 931 | raise ValueError(f"Unknown provider: {provider}") 932 | 933 | # Usage example 934 | async def main(): 935 | """Example usage of UniversalAIClient""" 936 | try: 937 | client = UniversalAIClient() 938 | 939 | # OpenAI request 940 | print("Requesting from OpenAI...") 941 | response = await client.chat('openai', 'Explain AI security in one sentence') 942 | print(f"OpenAI: {response}\n") 943 | 944 | # Claude request 945 | print("Requesting from Claude...") 946 | response = await client.chat( 947 | 'claude', 948 | 'Write a haiku about cybersecurity', 949 | max_tokens=100 950 | ) 951 | print(f"Claude: {response}\n") 952 | 953 | # Gemini request 954 | print("Requesting from Gemini...") 955 | response = await client.chat('gemini', 'What is prompt injection?') 956 | print(f"Gemini: {response}\n") 957 | 958 | except ValueError as e: 959 | print(f"Configuration error: {e}") 960 | except RuntimeError as e: 961 | print(f"API error: {e}") 962 | except Exception as e: 963 | print(f"Unexpected error: {e}") 964 | 965 | if __name__ == '__main__': 966 | asyncio.run(main()) 967 | ``` 968 | 969 | ### Node.js Implementation 970 | 971 | ```javascript 972 | /** 973 | * Universal AI API Client - Node.js Implementation 974 | * Supports OpenAI, Anthropic Claude, and Google Gemini 975 | */ 976 | const axios = require('axios'); 977 | require('dotenv').config(); 978 | 979 | class UniversalAIClient { 980 | constructor() { 981 | this.config = { 982 | openai: { 983 | url: 'https://api.openai.com/v1/chat/completions', 984 | key: process.env.OPENAI_API_KEY, 985 | headers: { 986 | 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, 987 | 'Content-Type': 'application/json' 988 | } 989 | }, 990 | claude: { 991 | url: 'https://api.anthropic.com/v1/messages', 992 | key: process.env.ANTHROPIC_API_KEY, 993 | headers: { 994 | 'x-api-key': process.env.ANTHROPIC_API_KEY, 995 | 'anthropic-version': '2023-06-01', 996 | 'Content-Type': 'application/json' 997 | } 998 | }, 999 | gemini: { 1000 | url: 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent', 1001 | key: process.env.GOOGLE_API_KEY 1002 | } 1003 | }; 1004 | 1005 | this.validateConfig(); 1006 | } 1007 | 1008 | validateConfig() { 1009 | for (const [provider, config] of Object.entries(this.config)) { 1010 | if (!config.key) { 1011 | throw new Error(`Missing API key for ${provider}`); 1012 | } 1013 | } 1014 | } 1015 | 1016 | async chat(provider, prompt, options = {}) { 1017 | if (!this.config[provider]) { 1018 | throw new Error(`Unknown provider: ${provider}`); 1019 | } 1020 | 1021 | const handlers = { 1022 | openai: () => this.callOpenAI(prompt, options), 1023 | claude: () => this.callClaude(prompt, options), 1024 | gemini: () => this.callGemini(prompt, options) 1025 | }; 1026 | 1027 | return await handlers[provider](); 1028 | } 1029 | 1030 | async callOpenAI(prompt, options) { 1031 | const payload = { 1032 | model: options.model || 'gpt-4o', 1033 | messages: [{ role: 'user', content: prompt }], 1034 | max_tokens: options.max_tokens || 1000, 1035 | temperature: options.temperature || 0.7 1036 | }; 1037 | 1038 | try { 1039 | const response = await axios.post( 1040 | this.config.openai.url, 1041 | payload, 1042 | { headers: this.config.openai.headers, timeout: 30000 } 1043 | ); 1044 | 1045 | return response.data.choices[0].message.content; 1046 | } catch (error) { 1047 | throw new Error(`OpenAI API error: ${error.message}`); 1048 | } 1049 | } 1050 | 1051 | async callClaude(prompt, options) { 1052 | const payload = { 1053 | model: options.model || 'claude-3-5-sonnet-20241022', 1054 | max_tokens: options.max_tokens || 1024, 1055 | messages: [{ role: 'user', content: prompt }] 1056 | }; 1057 | 1058 | try { 1059 | const response = await axios.post( 1060 | this.config.claude.url, 1061 | payload, 1062 | { headers: this.config.claude.headers, timeout: 30000 } 1063 | ); 1064 | 1065 | return response.data.content[0].text; 1066 | } catch (error) { 1067 | throw new Error(`Claude API error: ${error.message}`); 1068 | } 1069 | } 1070 | 1071 | async callGemini(prompt, options) { 1072 | const payload = { 1073 | contents: [{ parts: [{ text: prompt }] }], 1074 | generationConfig: { 1075 | temperature: options.temperature || 0.7, 1076 | maxOutputTokens: options.max_tokens || 1000 1077 | } 1078 | }; 1079 | 1080 | const url = `${this.config.gemini.url}?key=${this.config.gemini.key}`; 1081 | 1082 | try { 1083 | const response = await axios.post( 1084 | url, 1085 | payload, 1086 | { headers: { 'Content-Type': 'application/json' }, timeout: 30000 } 1087 | ); 1088 | 1089 | return response.data.candidates[0].content.parts[0].text; 1090 | } catch (error) { 1091 | throw new Error(`Gemini API error: ${error.message}`); 1092 | } 1093 | } 1094 | } 1095 | 1096 | // Usage example 1097 | (async () => { 1098 | try { 1099 | const client = new UniversalAIClient(); 1100 | 1101 | // OpenAI 1102 | console.log('Requesting from OpenAI...'); 1103 | const openaiResponse = await client.chat('openai', 'Explain AI security briefly'); 1104 | console.log(`OpenAI: ${openaiResponse}\n`); 1105 | 1106 | // Claude 1107 | console.log('Requesting from Claude...'); 1108 | const claudeResponse = await client.chat('claude', 'Write a haiku about security'); 1109 | console.log(`Claude: ${claudeResponse}\n`); 1110 | 1111 | // Gemini 1112 | console.log('Requesting from Gemini...'); 1113 | const geminiResponse = await client.chat('gemini', 'What is XSS?'); 1114 | console.log(`Gemini: ${geminiResponse}\n`); 1115 | 1116 | } catch (error) { 1117 | console.error(`Error: ${error.message}`); 1118 | } 1119 | })(); 1120 | 1121 | module.exports = UniversalAIClient; 1122 | ``` 1123 | 1124 | --- 1125 | 1126 | ### 🛡️ Security Checklist 1127 | 1128 | ### Backend Security 1129 | 1130 | - [ ] **API Keys in Environment Variables** - Never hardcode keys in source code 1131 | - [ ] **Input Validation & Sanitization** - Validate all user inputs before processing 1132 | - [ ] **Output Escaping (XSS Prevention)** - Sanitize LLM outputs before displaying 1133 | - [ ] **Rate Limiting Implemented** - Prevent abuse and control costs 1134 | - [ ] **HTTPS-only Communication** - Enforce encrypted connections 1135 | - [ ] **Error Handling without Info Leaks** - Don't expose sensitive error details 1136 | - [ ] **Logging without Secrets** - Never log API keys or sensitive data 1137 | - [ ] **CORS Configured Correctly** - Whitelist only trusted domains 1138 | - [ ] **Timeout Settings** - Prevent hanging requests 1139 | - [ ] **Request Size Limits** - Prevent oversized payloads 1140 | 1141 | ### Frontend Security 1142 | 1143 | - [ ] **No API Keys in Client** - All API calls go through backend 1144 | - [ ] **CSRF Protection (Nonces/Tokens)** - Implement anti-CSRF measures 1145 | - [ ] **Content Security Policy Headers** - Restrict resource loading 1146 | - [ ] **XSS Protection Active** - Escape all user-generated content 1147 | - [ ] **Sub-Resource Integrity (SRI)** - Verify external script integrity 1148 | - [ ] **No Sensitive Data in LocalStorage** - Use secure session storage 1149 | - [ ] **Input Length Limits** - Client-side validation before submission 1150 | 1151 | ### Production Security 1152 | 1153 | - [ ] **Secrets Management** - Use Vault, AWS Secrets Manager, or similar 1154 | - [ ] **API Key Rotation** - Regularly rotate keys 1155 | - [ ] **Monitoring & Alerting** - Track API usage and errors 1156 | - [ ] **Budget Limits at Providers** - Set spending caps 1157 | - [ ] **Audit Logs Enabled** - Track all API requests 1158 | - [ ] **Dependency Scanning** - Regular security audits of packages 1159 | - [ ] **WAF/DDoS Protection** - Implement web application firewall 1160 | - [ ] **Backup & Disaster Recovery** - Plan for service outages 1161 | 1162 | --- 1163 | 1164 | ### Provider-Specific Documentation 1165 | 1166 | ### OpenAI 1167 | - [Production Best Practices](https://platform.openai.com/docs/guides/production-best-practices) 1168 | - [Safety Best Practices](https://platform.openai.com/docs/guides/safety-best-practices) 1169 | - [Rate Limits](https://platform.openai.com/docs/guides/rate-limits) 1170 | 1171 | ### Anthropic Claude 1172 | - [API Key Best Practices](https://docs.anthropic.com/claude/docs/api-key-management) 1173 | - [Safety Guidelines](https://docs.anthropic.com/claude/docs/safety) 1174 | - [Rate Limits](https://docs.anthropic.com/claude/reference/rate-limits) 1175 | 1176 | ### Google Gemini 1177 | - [Security & Compliance](https://ai.google.dev/gemini-api/docs/security) 1178 | - [Safety Settings](https://ai.google.dev/gemini-api/docs/safety-settings) 1179 | - [API Quotas](https://ai.google.dev/gemini-api/docs/quota) 1180 | 1181 | --- 1182 | 1183 | ## 🔗 Additional Security Resources 1184 | 1185 | ### OWASP Resources 1186 | - [OWASP Top 10 for LLMs](https://owasp.org/www-project-top-10-for-large-language-model-applications/) 1187 | - [OWASP AI Security and Privacy Guide](https://owasp.org/www-project-ai-security-and-privacy-guide/) 1188 | - [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/) 1189 | 1190 | ### Standards & Frameworks 1191 | - [NIST AI Risk Management Framework](https://www.nist.gov/itl/ai-risk-management-framework) 1192 | - [ISO/IEC 42001 - AI Management System](https://www.iso.org/standard/81230.html) 1193 | 1194 | ### Security Tools 1195 | - [Gitleaks](https://github.com/gitleaks/gitleaks) - Scan for secrets in git repos 1196 | - [TruffleHog](https://github.com/trufflesecurity/trufflehog) - Find leaked credentials 1197 | - [Dependabot](https://github.com/dependabot) - Automated dependency updates 1198 | 1199 | ### Good to know 1200 | 1201 | - [Security Headers — Complete Implementation Guide](https://github.com/VolkanSah/Security-Headers) 1202 | - [Securing FastAPI Applications](https://github.com/VolkanSah/Securing-FastAPI-Applications) 1203 | - [ModSecurity Webserver Protection Guide](https://github.com/VolkanSah/ModSecurity-Webserver-Protection-Guide) 1204 | 1205 | --- 1206 | 1207 | ## 💖 Support & Contributions 1208 | 1209 | Found this useful? 1210 | 1211 | - ⭐ **Star this repo** to show support 1212 | - 🐛 **Report Issues** via GitHub Issues 1213 | - 💡 **Suggest Improvements** through Pull Requests 1214 | - 🔀 **Contribute** - PRs are welcome! 1215 | - 📢 **Share** with your developer community 1216 | - **If you are rich** buy me a ☕ 1217 | 1218 | ### Contributors 1219 | 1220 | Thank you to all contributors who help improve this guide! 1221 | 1222 | --- 1223 | 1224 | ## 📝 License 1225 | 1226 | MIT License - see [LICENSE](LICENSE) 1227 | 1228 | Copyright (c) 2025 Volkan Kücükbudak 1229 | 1230 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 1231 | 1232 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 1233 | 1234 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1235 | 1236 | --- 1237 | 1238 | ## ⚠️ Disclaimer 1239 | 1240 | These best practices are **recommendations based on current industry standards**. 1241 | 1242 | **You are responsible for:** 1243 | - The security of your implementation 1244 | - Compliance with applicable laws and regulations 1245 | - Regular security audits and updates 1246 | - Monitoring and responding to security incidents 1247 | 1248 | **No guarantees:** 1249 | - This guide does not guarantee complete security 1250 | - Security is a continuously evolving field 1251 | - New vulnerabilities may emerge 1252 | - Always stay informed about latest threats 1253 | 1254 | **Professional advice:** 1255 | - For production systems handling sensitive data, consult security professionals 1256 | - Conduct regular penetration testing 1257 | - Implement defense-in-depth strategies 1258 | - Have an incident response plan 1259 | 1260 | --- 1261 | 1262 | **Stay vigilant. Stay secure. 🔒** 1263 | 1264 |

1265 | Made with 💀 for the WordPress & TYPO3 Community
1266 | Maintained by Volkan Kücükbudak
1267 | Last Updated: December 2025 1268 |

1269 | --------------------------------------------------------------------------------