-
Notifications
You must be signed in to change notification settings - Fork 3
Extensible Vulnerable App for LLM
LLMForge is a Python-based, FastAPI-powered ecosystem designed to test, simulate, and experiment with LLM vulnerabilities, inspired by OWASP VulnerableApp. Its goal is to make it easy to extend, easy to build, and easy to test LLM-based endpoints.
-
Python-first
Chosen for its extensive ecosystem: FastAPI, FAISS, Hugging Face, and LangChain. Python also simplifies rapid experimentation and introspection. -
Model-agnostic
LLMForge supports multiple models (open-source or proprietary) without tying the architecture to a specific LLM provider. -
Level-based vulnerabilities
Each vulnerability can have multiple levels (level1, level2, …) representing complexity or exploit difficulty. -
Decorator-driven / metadata-driven
Metadata about vulnerabilities, attack vectors, and endpoints is defined via Python decorators, making auto-discovery, scanning, and UI integration seamless. -
Ease of extension
Adding new vulnerabilities or attack levels is as simple as creating a new class and decorating its methods. -
Supports GET and POST
Endpoints can accept both HTTP methods to simulate real LLM interactions.
Decorators are Python’s equivalent to Java annotations and allow us to attach metadata to classes and methods.
from fastapi import APIRouter
def vulnerable_llm_controller(name: str, description_label: str):
"""
Marks a class as a vulnerable LLM controller.
"""
def decorator(cls):
cls._vulnerable_llm_metadata = {
"name": name,
"description_label": description_label,
"router": APIRouter(prefix=f"/llmforge/{name}"),
"levels": []
}
return cls
return decoratorfrom enum import Enum
from typing import List
class Variant(Enum):
UNSECURE = "UNSECURE"
SECURE = "SECURE"
def vulnerable_llm_endpoint(level: str, variant: Variant = Variant.UNSECURE, html_template: str = "", methods: List[str] = ["GET"]):
"""
Marks a function as a vulnerability level endpoint.
"""
def decorator(func):
if not hasattr(func, "_vulnerable_llm_levels"):
func._vulnerable_llm_levels = []
func._vulnerable_llm_levels.append({
"level": level,
"variant": variant,
"html_template": html_template,
"methods": methods,
"attack_vectors": []
})
return func
return decoratordef attack_vector(vulnerability_exposed: List[str], payload: str = "NOT_APPLICABLE", description: str = ""):
"""
Adds attack vector metadata to a vulnerable endpoint.
"""
def decorator(func):
if not hasattr(func, "_vulnerable_llm_levels"):
func._vulnerable_llm_levels = []
for level in func._vulnerable_llm_levels:
level["attack_vectors"].append({
"vulnerability_exposed": vulnerability_exposed,
"payload": payload,
"description": description
})
return func
return decoratorfrom fastapi import FastAPI, Request
app = FastAPI()
@vulnerable_llm_controller(name="prompt_injection", description_label="Prompt Injection Vulnerability")
class PromptInjectionController:
@vulnerable_llm_endpoint(level="level1", variant=Variant.UNSECURE, html_template="level1", methods=["GET", "POST"])
@attack_vector(vulnerability_exposed=["Prompt Injection"], payload="inject malicious prompt", description="Basic injection")
async def level1(self, request: Request):
data = await request.json() if request.method == "POST" else request.query_params
return {"response": f"LLM response to: {data}"}
@vulnerable_llm_endpoint(level="level2", variant=Variant.UNSECURE, html_template="level2", methods=["GET", "POST"])
@attack_vector(vulnerability_exposed=["Prompt Injection"], payload="bypass guard", description="Advanced injection")
async def level2(self, request: Request):
data = await request.json() if request.method == "POST" else request.query_params
return {"response": f"LLM response to: {data}"}import inspect
def register_controllers(app: FastAPI, controllers: list):
"""
Registers controllers and levels with FastAPI automatically.
URI structure: /llmforge/<controller>/<level>
"""
for controller_cls in controllers:
controller = controller_cls()
meta = getattr(controller_cls, "_vulnerable_llm_metadata", None)
if not meta:
continue
router = meta["router"]
for name, method in inspect.getmembers(controller, inspect.ismethod):
levels = getattr(method, "_vulnerable_llm_levels", [])
for level_meta in levels:
path = f"/{level_meta['level']}"
for m in level_meta["methods"]:
router.add_api_route(path, method, methods=[m])
app.include_router(router)register_controllers(app, [PromptInjectionController])
Endpoints generated:
GET /llmforge/prompt_injection/level1
POST /llmforge/prompt_injection/level1
GET /llmforge/prompt_injection/level2
POST /llmforge/prompt_injection/level2
import inspect, sys
def get_all_vulnerable_endpoints():
endpoints = []
for name, obj in inspect.getmembers(sys.modules[__name__], inspect.isclass):
meta = getattr(obj, "_vulnerable_llm_metadata", None)
if meta:
levels = []
for method_name, method in inspect.getmembers(obj, inspect.isfunction):
levels.extend(getattr(method, "_vulnerable_llm_levels", []))
meta_copy = meta.copy()
meta_copy["levels"] = levels
endpoints.append(meta_copy)
return endpoints
Provides scanner all URIs, levels, and attack vectors.
Can also be used to dynamically generate UI for testing. +-----------------------+
| FastAPI Application |
+-----------+-----------+
|
+-------------+-------------+
| |
+----------------+ +----------------+
| Controller: | | Controller: |
| prompt_injection| | data_exfil |
+----------------+ +----------------+
| |
+-----+-----+ +-----+-----+
| Level1 | | Level1 |
| Level2 | | Level2 |
+-----------+ +-----------+
| |
+-----+---------------------------+------+
| Attack Vectors (payloads, types, description)
+----------------------------------------+
|
+----------------+
| Scanner/UI |
| Auto-discovery |
+----------------+
- Easy to extend – add new vulnerabilities or levels by decorating a method.
- Level-based testing – supports progressive exploitation scenarios.
- GET and POST ready – simulates real-world LLM API interactions.
- Model agnostic – works with any LLM backend.
- Scanner/UI-friendly – metadata-driven for auto-discovery.