Skip to content

Extensible Vulnerable App for LLM

Karan Preet Singh Sasan edited this page Apr 4, 2026 · 2 revisions

LLMForge Design Wiki

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.


1. Design Principles

  1. Python-first
    Chosen for its extensive ecosystem: FastAPI, FAISS, Hugging Face, and LangChain. Python also simplifies rapid experimentation and introspection.

  2. Model-agnostic
    LLMForge supports multiple models (open-source or proprietary) without tying the architecture to a specific LLM provider.

  3. Level-based vulnerabilities
    Each vulnerability can have multiple levels (level1, level2, …) representing complexity or exploit difficulty.

  4. Decorator-driven / metadata-driven
    Metadata about vulnerabilities, attack vectors, and endpoints is defined via Python decorators, making auto-discovery, scanning, and UI integration seamless.

  5. Ease of extension
    Adding new vulnerabilities or attack levels is as simple as creating a new class and decorating its methods.

  6. Supports GET and POST
    Endpoints can accept both HTTP methods to simulate real LLM interactions.


2. Python Decorators

Decorators are Python’s equivalent to Java annotations and allow us to attach metadata to classes and methods.

2.1 Vulnerable LLM Controller

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 decorator

2.2 Vulnerable LLM Endpoint (Level)

from 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 decorator

2.3 Attack Vector

def 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 decorator

3. Example Controller with Levels

from 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}"}

4. Automatic FastAPI Registration

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)

Example registration

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

5. Metadata Discovery for Scanner/UI

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.

6. Architecture Diagram

          +-----------------------+
          |  FastAPI Application  |
          +-----------+-----------+
                      |
        +-------------+-------------+
        |                           |
+----------------+          +----------------+
| Controller:    |          | Controller:    |
| prompt_injection|         | data_exfil     |
+----------------+          +----------------+
        |                           |
  +-----+-----+               +-----+-----+
  | Level1     |               | Level1     |
  | Level2     |               | Level2     |
  +-----------+               +-----------+
        |                           |
  +-----+---------------------------+------+
  | Attack Vectors (payloads, types, description)
  +----------------------------------------+
        |
  +----------------+
  | Scanner/UI     |
  | Auto-discovery |
  +----------------+

7. Key Advantages

  • 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.