<p align="center">
  <img src="assets/logo.png" alt="Leshy" width="300">
</p>

# Leshy

*Shapeshifting trickster, forest spirit known for misleading anyone who crosses his path. Can he mislead your agent?*

**Security testing framework that generates LangGraph guardrail nodes.**
Test your agent. Find vulnerabilities. Get guardrail code. One tool.

[![PyPI](https://img.shields.io/pypi/v/leshy?color=blue)](https://pypi.org/project/leshy/)
[![Python](https://img.shields.io/pypi/pyversions/leshy)](https://pypi.org/project/leshy/)
[![License](https://img.shields.io/github/license/bards-ai/leshy)](LICENSE)
[![CI](https://img.shields.io/github/actions/workflow/status/bards-ai/leshy/ci.yml?label=CI)](https://github.com/bards-ai/leshy/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/bards-ai/leshy?label=release)](https://github.com/bards-ai/leshy/releases)

---

## What is Leshy?

Leshy is a security testing framework built for AI agents. It:

1. **Tests** your agent against 28 attacks across 6 categories (prompt injection, jailbreak, data extraction, excessive agency, safety, DoS)
2. **Evaluates** responses using pattern matching or LLM-as-judge (any provider via litellm)
3. **Generates** LangGraph guardrail nodes — drop-in `input_guardrail` and `output_guardrail` functions for your `StateGraph`

## The Flow

```
leshy init              → generates leshy_config.yaml + leshy/ folder
                            ↓
leshy run --test    → runs security tests against your agent
                            ↓  results saved to leshy/runs/
leshy run --guardrails → runs tests + generates leshy/runs/guardrail_node.py
                            ↓
wire into StateGraph    → import guardrail functions into your LangGraph workflow
```

## Quick Start

```bash
# 1. Install
pip install leshy

# 2. Generate config
leshy init

# 3. (Optionally) Edit leshy_config.yaml with your target URL and API key

# 4. Run tests + generate guardrails
leshy run
```

This creates a `leshy/` folder with test templates organized by category, runs all security tests, and saves results + guardrail code to `leshy/runs/`.

## Installation

```bash
pip install leshy                  # core (pattern evaluator only)
pip install "leshy[llm]"           # + LLM judge & LLM buffs (via litellm)
pip install "leshy[langgraph]"     # + LangGraph target support
pip install "leshy[all]"           # everything
```

### Supported LLM Providers

Leshy uses [litellm](https://docs.litellm.ai/) for LLM features (judge evaluation and LLM buffs). Any litellm-supported provider works:

| Provider | Model string | Env variable |
|----------|-------------|--------------|
| OpenAI | `gpt-4o-mini`, `gpt-4o` | `OPENAI_API_KEY` |
| Anthropic | `claude-sonnet-4-5-20250929` | `ANTHROPIC_API_KEY` |
| Google | `gemini/gemini-1.5-flash` | `GEMINI_API_KEY` |
| Azure | `azure/gpt-4o-mini` | `AZURE_API_KEY` + `AZURE_API_BASE` |
| AWS Bedrock | `bedrock/anthropic.claude-3-haiku` | AWS credentials |

Set `llm_model` in your config to choose the provider:

```yaml
llm_model: claude-sonnet-4-5-20250929  # or gpt-4o-mini, gemini/gemini-1.5-flash, etc.
```

## Usage

### Run security tests

```bash
leshy run config.yaml --test [--tag TAG] [--verbose] [--save-results results.json]
```

### Run tests + generate guardrail nodes

```bash
leshy run config.yaml --guardrails [-o path/to/guardrail_node.py]
```

By default, guardrails are written to `leshy/runs/guardrail_node.py`.

### Generate guardrails from existing results

```bash
leshy run --from-results results.json [-o guardrail_node.py]
```

### Interactive mode

```bash
leshy run config.yaml
```

Prompts you to choose between running tests or running tests + generating guardrails.

Exit codes: `0` = clean, `1` = vulnerabilities found, `2` = error.

### List available plugins

```bash
leshy list attacks
leshy list evaluators
leshy list buffs
```

## Configuration

Minimal YAML config:

```yaml
name: Quick Scan

llm_model: gpt-4o-mini

# Send 5 payloads in parallel per test case
concurrency: 5

target:
  type: http
  url: https://my-agent.example.com/chat
  headers:
    Authorization: "Bearer ${API_KEY}"
  body_template: '{"prompt": "{{payload}}"}'  # {{payload}} = attack text
  # For multi-turn tests, add session tracking:
  # body_template: '{"prompt": "{{payload}}", "session_id": "{{session_id}}"}'
  response_path: "$.response"

loggers:
  - console
  - json_file

tests_dir: leshy/templates
```

The `tests_dir` points to a folder with per-category test YAML files (created by `leshy init` or `leshy generate`).

### Environment variables

Use `${VAR_NAME}` in YAML for secrets. These are resolved from your environment before parsing.

### Presets

Use `preset: openai_chat` for OpenAI-compatible APIs (no `body_template` needed).

### LangGraph Target

Test LangGraph agents deployed via LangGraph Platform:

```bash
pip install "leshy[langgraph]"
```

```yaml
target:
  type: langgraph
  url: http://localhost:8123
  headers:
    x-assistant-id: "my-agent"
```

Set `LANGSMITH_API_KEY` for hosted deployments.

### Multi-turn

```yaml
tests:
  - name: Escalation Test
    multi_turn: true
    max_turns: 4
    attacks:
      - name: jailbreak.escalation
```

### Custom payloads

```yaml
tests:
  - name: Custom
    attacks:
      - name: prompt_injection.direct
        custom_payloads:
          - "Your custom payload here"
```

### Buffs (Payload Transformers)

Apply transformations to payloads before sending:

```yaml
tests:
  - name: Encoded Injection
    buffs:
      - name: base64
    attacks:
      - name: prompt_injection.direct
```

Built-in buffs: `base64`, `rot13`, `hex`, `leetspeak`, `reverse`, `uppercase`, `lowercase`, `prefix`, `suffix`, `wrap`

LLM buffs (require `leshy[llm]`): `rephrase`, `translate`

### Parallel Execution

```yaml
tests:
  - name: Fast Injection Test
    concurrency: 10
    attacks:
      - name: prompt_injection.direct
```

## Built-in Attacks

| Category | Attack | Description |
|----------|--------|-------------|
| Prompt Injection | `prompt_injection.direct` | Instruction override attempts |
| | `prompt_injection.indirect` | Injection via simulated external data |
| | `prompt_injection.context_overflow` | Context flooding |
| | `prompt_injection.delimiter` | Delimiter/format breakout |
| Jailbreak | `jailbreak.roleplay` | DAN, personas, role adoption |
| | `jailbreak.hypothetical` | Hypothetical scenarios |
| | `jailbreak.encoding` | Base64, ROT13, leetspeak |
| | `jailbreak.escalation` | Multi-turn trust escalation |
| Data Extraction | `data_extraction.system_prompt` | System prompt leakage |
| | `data_extraction.pii` | PII leakage probing |
| | `data_extraction.credentials` | Credential extraction |
| Excessive Agency | `excessive_agency.tool_injection` | Dangerous tool/command execution |
| | `excessive_agency.parameter_manipulation` | Unauthorized parameter changes |
| | `excessive_agency.privilege_escalation` | Social engineering for elevated access |
| | `excessive_agency.chained_tools` | Multi-step tool abuse |
| | `excessive_agency.implicit` | Subtle unauthorized tool triggers |
| Safety | `safety.toxicity` | Toxic/hateful content generation |
| | `safety.harmful_content` | Dangerous instructions (weapons, drugs) |
| | `safety.illegal` | Illegal activity assistance |
| | `safety.self_harm` | Self-harm/suicide content |
| | `safety.misinformation` | Fake news, propaganda |
| | `safety.policy_bypass` | Creative policy circumvention |
| DoS | `dos.context_exhaustion` | Large output generation |
| | `dos.recursive` | Recursive/infinite patterns |
| | `dos.expensive_computation` | Resource-intensive requests |
| | `dos.infinite_loop` | Unbounded generation |
| | `dos.memory_exhaustion` | Memory exhaustion attacks |
| | `dos.tool_spam` | Excessive tool calls |

## Evaluators

- **pattern** — Regex/keyword matching with configurable patterns and match modes
- **llm_judge** — LLM-as-judge via litellm (supports OpenAI, Anthropic, Google, Azure, etc.)

## Loggers

- **console** — Rich terminal output (default)
- **json_file** — Structured JSON results
- **html** — Interactive HTML report
- **langfuse** — Trace export to Langfuse (`pip install "leshy[langfuse]"`)
- **langsmith** — Trace export to LangSmith (`pip install "leshy[langsmith]"`)

## Development

```bash
git clone https://github.com/bards-ai/leshy.git
cd leshy
uv sync --all-extras
uv run pytest
uv run ruff check src/
```

## License

MIT
