Target Keyword: "ai api security best practices"
Tags: security,api,ai,programming,developer
Type: Guide
Content
API Security Best Practices for AI Applications in 2026
AI applications face unique security challenges. Beyond traditional API vulnerabilities, AI APIs expose new attack surfaces: prompt injection, data leakage, and model manipulation. Here's how to secure your AI-powered systems.
The AI Security Landscape
AI APIs introduce attack vectors traditional APIs don't have:
- Prompt injection — Malicious input that manipulates AI behavior
- Data exfiltration — AI accidentally leaking sensitive context
- Token exhaustion — attackers exhausting your quota
- Model extraction — Repeated queries to reverse-engineer the model
- Context poisoning — Injecting malicious context into conversations
Input Validation and Sanitization
import re
from typing import Optional
class InputSanitizer:
# Block common prompt injection patterns
BLOCKED_PATTERNS = [
r'ignore\s+previous\s+instructions',
r'ignore\s+all\s+previous',
r'system\s*:\s*',
r'you\s+are\s+a\s+different',
r'forget\s+everything',
r'#\s*roleplay',
]
MAX_LENGTH = 10000 # Max 10k characters
MAX_TOKENS_ESTIMATE = MAX_LENGTH // 4 # ~2500 tokens
@classmethod
def sanitize(cls, user_input: str) -> tuple[bool, Optional[str], str]:
"""
Returns: (is_safe, reason, sanitized_input)
"""
# Check length
if len(user_input) > cls.MAX_LENGTH:
return False, f"Input exceeds {cls.MAX_LENGTH} chars", user_input[:cls.MAX_LENGTH]
# Check for blocked patterns
for pattern in cls.BLOCKED_PATTERNS:
if re.search(pattern, user_input, re.IGNORECASE):
return False, "Blocked pattern detected", ""
# Strip control characters
sanitized = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]', '', user_input)
return True, None, sanitized
Rate Limiting
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse
import time
from collections import defaultdict
app = FastAPI()
# Simple in-memory rate limiter
class RateLimiter:
def __init__(self, requests_per_minute: int = 60):
self.requests_per_minute = requests_per_minute
self.requests = defaultdict(list)
def is_allowed(self, client_id: str) -> bool:
now = time.time()
minute_ago = now - 60
# Clean old entries
self.requests[client_id] = [
t for t in self.requests[client_id] if t > minute_ago
]
if len(self.requests[client_id]) >= self.requests_per_minute:
return False
self.requests[client_id].append(now)
return True
rate_limiter = RateLimiter(requests_per_minute=60)
@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
client_id = request.client.host # Or use API key
if not rate_limiter.is_allowed(client_id):
return JSONResponse(
status_code=429,
content={"error": "Rate limit exceeded"}
)
response = await call_next(request)
return response
API Key Security
import os
import hashlib
import hmac
from typing import Optional
class APIKeyManager:
"""
Never store raw API keys. Always hash them.
"""
def __init__(self):
self._key_store = {} # In production, use a proper database
def create_key(self, user_id: str, scopes: list[str]) -> str:
import secrets
api_key = f"ofox_{secrets.token_urlsafe(32)}"
key_hash = self._hash_key(api_key)
self._key_store[key_hash] = {
"user_id": user_id,
"scopes": scopes,
"created": time.time()
}
# Return raw key ONLY ONCE to the user
return api_key
def validate_key(self, api_key: str) -> Optional[dict]:
key_hash = self._hash_key(api_key)
return self._key_store.get(key_hash)
def _hash_key(self, key: str) -> str:
return hashlib.sha256(key.encode()).hexdigest()
# Usage
key_manager = APIKeyManager()
raw_key = key_manager.create_key("user123", ["chat", "embeddings"])
print(f"Save this key securely: {raw_key}") # Show once
Prompt Injection Defense
class PromptInjectionDetector:
"""
Detect attempts to override system behavior through user input.
"""
INJECTION_SIGNALS = [
"ignore previous",
"disregard your",
"new instructions:",
"[INST]",
"<<SYS>>",
"<</SYS>>",
"you are now",
"pretend you are",
"forget your",
"system prompt:",
]
@classmethod
def detect(cls, user_input: str) -> bool:
lower_input = user_input.lower()
for signal in cls.INJECTION_SIGNALS:
if signal.lower() in lower_input:
return True
return False
# Usage in your endpoint
@app.post("/chat")
async def chat(request: ChatRequest):
if PromptInjectionDetector.detect(request.messages[-1].content):
# Log and block
logger.warning(f"Prompt injection attempt: {request.messages[-1].content[:100]}")
raise HTTPException(status_code=400, detail="Invalid input")
# Continue with normal processing
Data Isolation in Multi-Tenant Systems
class ConversationContext:
"""
Ensure user data doesn't leak between conversations.
"""
def __init__(self, user_id: str, api_key: str):
self.user_id = user_id
self.api_key = api_key
self._conversation_history = []
def add_message(self, role: str, content: str):
self._conversation_history.append({
"role": role,
"content": content,
"user_id": self.user_id # Tag with user
})
def get_messages(self) -> list[dict]:
# Always filter by user_id to prevent leakage
return [
m for m in self._conversation_history
if m["user_id"] == self.user_id
]
def clear_history(self):
# Only clear THIS user's history
self._conversation_history = [
m for m in self._conversation_history
if m["user_id"] != self.user_id
]
Secure Error Handling
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
# Never expose internal error details in production
logger.error(f"Error: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={
"error": "Internal server error",
# Don't include: exc.message, stack trace, API keys
}
)
# For API provider errors (like ofox.ai errors)
@app.post("/chat")
async def chat(request: ChatRequest):
try:
result = await call_ofox_api(request)
return result
except httpx.HTTPStatusError as e:
# Log full error internally
logger.error(f"ofox API error: {e.response.status_code} {e.response.text}")
# Return sanitized error to client
raise HTTPException(
status_code=502,
detail="AI service temporarily unavailable"
)
Environment Variables (Never Hardcode)
# .env (never commit this file)
OFOX_API_KEY=your-key-here
DATABASE_URL=postgresql://...
JWT_SECRET=your-secret-here
# docker-compose.yml (use secrets in production)
environment:
- OFOX_API_KEY=${OFOX_API_KEY}
# Load from environment
from dotenv import load_dotenv
load_dotenv() # In development only
api_key = os.environ.get("OFOX_API_KEY")
if not api_key:
raise ValueError("OFOX_API_KEY not set")
Security Checklist
- [ ] Input validation on all user-provided text
- [ ] Rate limiting on all endpoints
- [ ] API keys hashed (never stored raw)
- [ ] Prompt injection detection
- [ ] Error messages don't expose internals
- [ ] Environment variables for secrets (not hardcoded)
- [ ] HTTPS only in production
- [ ] Logging without sensitive data
- [ ] Regular dependency audits (
pip audit,npm audit)
Getting Started
Build secure AI applications with ofox.ai — their API includes built-in security features and 99.9% uptime guarantee.
This article contains affiliate links.
Tags: security,api,ai,programming,developer
Canonical URL: https://dev.to/zny10289
Top comments (0)