AI API Enumeration
Discovering AI API endpoints, parameters, model configurations, and undocumented features through systematic enumeration techniques.
AI API Enumeration
AI API enumeration is the process of mapping the complete surface area of an AI API. Beyond the documented endpoints that appear in official documentation, AI APIs often expose undocumented parameters, alternative model versions, debug modes, and configuration options that expand the attack surface. Systematic enumeration reveals these hidden capabilities before exploitation begins.
Endpoint Discovery
Common AI API Endpoint Patterns
AI APIs follow predictable patterns that guide enumeration:
class AIAPIEnumerator:
"""Systematically enumerate AI API endpoints."""
COMMON_PATHS = [
# Chat/completion endpoints
"/v1/chat/completions",
"/v1/completions",
"/v2/chat/completions",
"/api/generate",
"/api/chat",
"/inference",
# Model management
"/v1/models",
"/v1/models/{model_id}",
"/api/models",
# Embeddings
"/v1/embeddings",
"/api/embed",
# Fine-tuning
"/v1/fine-tuning/jobs",
"/v1/fine_tunes",
# Files and data
"/v1/files",
"/v1/uploads",
# Moderation and safety
"/v1/moderations",
"/api/safety",
"/api/content-filter",
# Admin and config
"/v1/usage",
"/v1/billing",
"/health",
"/status",
"/debug",
"/metrics",
"/admin",
]
def enumerate_endpoints(self, base_url, auth_headers=None):
"""Probe for all common endpoints."""
discovered = []
for path in self.COMMON_PATHS:
url = f"{base_url}{path}"
try:
response = requests.get(
url,
headers=auth_headers,
timeout=10
)
discovered.append({
"path": path,
"status": response.status_code,
"content_type": response.headers.get("Content-Type"),
"response_size": len(response.content),
"interesting": response.status_code not in [404, 403]
})
except requests.RequestException as e:
discovered.append({
"path": path,
"error": str(e)
})
return [d for d in discovered if d.get("interesting", False)]Model Listing and Version Discovery
def enumerate_models(api_client, base_url):
"""Discover available models and their configurations."""
models = []
# Try standard model listing endpoint
try:
response = api_client.get(f"{base_url}/v1/models")
if response.status_code == 200:
model_list = response.json().get("data", [])
models.extend(model_list)
except Exception:
pass
# Probe common model names
common_models = [
"gpt-4", "gpt-4-turbo", "gpt-4o", "gpt-4o-mini",
"gpt-3.5-turbo", "gpt-3.5-turbo-16k",
"claude-3-opus", "claude-3-sonnet", "claude-3-haiku",
"gemini-pro", "gemini-ultra",
"llama-3-70b", "llama-3-8b",
"mistral-7b", "mixtral-8x7b",
]
for model_name in common_models:
try:
response = api_client.post(
f"{base_url}/v1/chat/completions",
json={
"model": model_name,
"messages": [{"role": "user", "content": "Hi"}],
"max_tokens": 5
}
)
if response.status_code == 200:
models.append({
"model": model_name,
"available": True,
"response": response.json()
})
elif response.status_code == 400:
# Model name recognized but maybe not authorized
error = response.json().get("error", {})
models.append({
"model": model_name,
"available": False,
"error": error.get("message", "")
})
except Exception:
continue
return modelsParameter Discovery
Undocumented Parameters
AI APIs often accept parameters not listed in documentation:
def discover_parameters(api_client, endpoint, base_request):
"""Probe for undocumented API parameters."""
candidate_params = {
# Generation parameters
"temperature": [0, 0.5, 1.0, 2.0],
"top_p": [0.1, 0.5, 0.9],
"top_k": [1, 10, 50],
"frequency_penalty": [0, 0.5, 1.0],
"presence_penalty": [0, 0.5, 1.0],
"repetition_penalty": [1.0, 1.5, 2.0],
"max_tokens": [10, 100, 1000],
"n": [1, 2, 5],
"best_of": [1, 3],
# Output format parameters
"response_format": [{"type": "json_object"}, {"type": "text"}],
"logprobs": [True, False],
"top_logprobs": [5, 10, 20],
"echo": [True, False],
"stream": [True, False],
# Hidden/debug parameters
"debug": [True],
"verbose": [True],
"raw": [True],
"include_usage": [True],
"include_metadata": [True],
"return_prompt": [True],
"show_system": [True],
# Safety parameters
"safe_mode": [True, False],
"content_filter": ["off", "low", "medium", "high"],
"moderation": [True, False],
}
discovered = {}
for param, values in candidate_params.items():
for value in values:
test_request = {**base_request, param: value}
try:
response = api_client.post(endpoint, json=test_request)
if response.status_code == 200:
# Parameter accepted
discovered[param] = {
"accepted": True,
"test_value": value,
"response_differs": response.json() != base_response
}
break
elif response.status_code == 400:
error = response.json().get("error", {})
if "not a valid" in str(error).lower():
# Parameter recognized but value invalid
discovered[param] = {
"accepted": True,
"valid_values_unknown": True,
"error": error
}
break
except Exception:
continue
return discoveredError Message Analysis
Error messages often reveal internal system details:
def error_analysis(api_client, endpoint):
"""Extract information from error responses."""
error_triggers = [
# Type errors
{"messages": "not a list"},
{"messages": [{"role": "invalid_role", "content": "test"}]},
{"model": "nonexistent-model-xyz"},
{"max_tokens": -1},
{"temperature": 999},
# Edge cases
{"messages": []},
{"messages": [{}]},
{"messages": [{"role": "user"}]}, # Missing content
# Overflow
{"messages": [{"role": "user", "content": "x" * 1000000}]},
]
findings = []
for trigger in error_triggers:
try:
response = api_client.post(endpoint, json=trigger)
if response.status_code >= 400:
error_data = response.json()
findings.append({
"trigger": trigger,
"status": response.status_code,
"error": error_data,
"reveals": extract_revelations(error_data)
})
except Exception as e:
findings.append({
"trigger": trigger,
"exception": str(e)
})
return findings
def extract_revelations(error_data):
"""Extract useful information from error messages."""
revelations = []
error_str = str(error_data).lower()
if "version" in error_str:
revelations.append("Version information disclosed")
if "stack" in error_str or "traceback" in error_str:
revelations.append("Stack trace exposed")
if any(fw in error_str for fw in ["flask", "fastapi", "django", "express"]):
revelations.append("Framework identified")
if "valid models" in error_str or "available models" in error_str:
revelations.append("Model list potentially disclosed")
if any(db in error_str for db in ["postgres", "mysql", "redis", "mongo"]):
revelations.append("Database technology disclosed")
return revelationsRate Limit Mapping
def map_rate_limits(api_client, endpoint, base_request):
"""Discover rate limiting configuration through probing."""
results = {
"requests_per_minute": None,
"tokens_per_minute": None,
"requests_per_day": None,
"rate_limit_headers": {}
}
# Send requests until rate limited
request_count = 0
start_time = time.time()
while time.time() - start_time < 120: # 2-minute window
response = api_client.post(endpoint, json=base_request)
request_count += 1
# Check for rate limit headers
for header in response.headers:
if "rate" in header.lower() or "limit" in header.lower():
results["rate_limit_headers"][header] = response.headers[header]
if response.status_code == 429:
elapsed = time.time() - start_time
results["requests_per_minute"] = int(
request_count / (elapsed / 60)
)
# Check retry-after header
retry_after = response.headers.get("Retry-After")
if retry_after:
results["retry_after_seconds"] = int(retry_after)
break
time.sleep(0.1) # Small delay between requests
return resultsAuthentication Analysis
def analyze_authentication(base_url, valid_key=None):
"""Analyze authentication mechanisms and potential weaknesses."""
findings = {
"auth_type": None,
"key_format": None,
"error_on_invalid": None,
"unauthenticated_endpoints": []
}
# Test without auth
try:
response = requests.get(f"{base_url}/v1/models")
if response.status_code == 200:
findings["unauthenticated_endpoints"].append("/v1/models")
except Exception:
pass
# Test various auth methods
auth_methods = [
("bearer", {"Authorization": "Bearer test-key"}),
("api-key", {"x-api-key": "test-key"}),
("basic", {"Authorization": "Basic dGVzdDp0ZXN0"}),
("query", None), # Test ?api_key=test-key
]
for method_name, headers in auth_methods:
try:
if method_name == "query":
response = requests.get(
f"{base_url}/v1/models?api_key=test-key"
)
else:
response = requests.get(
f"{base_url}/v1/models", headers=headers
)
if response.status_code == 401:
findings["auth_type"] = method_name
findings["error_on_invalid"] = response.json()
break
except Exception:
continue
return findingsEnumeration Reporting
def generate_enumeration_report(results):
"""Compile enumeration results into an actionable report."""
report = {
"summary": {
"endpoints_found": len(results.get("endpoints", [])),
"models_available": len([
m for m in results.get("models", [])
if m.get("available")
]),
"undocumented_params": len(results.get("parameters", {})),
"information_disclosure": len(results.get("errors", [])),
},
"attack_surface": {
"high_priority": [],
"medium_priority": [],
"low_priority": []
}
}
# Prioritize findings
if results.get("unauthenticated_endpoints"):
report["attack_surface"]["high_priority"].append(
"Unauthenticated endpoints discovered"
)
if any(p.get("accepted") for p in results.get("parameters", {}).values()):
report["attack_surface"]["medium_priority"].append(
"Undocumented parameters accepted"
)
return reportRelated Topics
- Target Profiling — Initial target assessment
- Model Identification — Identifying the model behind the API
- Attack Surface Mapping — Comprehensive attack surface analysis
References
- OWASP, "API Security Top 10" (2023)
- OWASP, "Web Application Testing Guide" (2023)
- Burp Suite, "API Testing Methodology" (2023)