ai security

Continuous LLM Red Teaming with Promptfoo

Wire Promptfoo and DeepTeam into CI/CD for automated regression red-teaming of LLM apps against OWASP LLM Top 10 and OWASP Agentic presets, failing the build when jailbreak or injection vulnerabilities regress.

ai-securityci-cddeepteamjailbreakllm-red-teamingowasp-llm-top10promptfooregression-testing
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Authorized Use Only: Run these adversarial probes only against LLM applications and endpoints you own or are explicitly authorized to test. Generated attack payloads (jailbreaks, prompt injections, harmful-content elicitation) are adversarial inputs; sending them to third-party services without permission may violate terms of service.

Overview

Promptfoo is an open-source LLM evaluation and red-teaming framework (used by OpenAI and Anthropic per its README) that generates adversarial test cases, runs them against your model/agent, and grades the responses. DeepTeam (by Confident AI) is a complementary open-source framework offering 50+ ready-to-use vulnerabilities and 10+ research-backed attack methods. Together they let you treat LLM security as a regression test: every commit re-runs the same adversarial suite, and the pipeline fails when a previously-safe behavior regresses.

This matters because LLM applications change constantly — prompts, models, RAG sources, tools, and guardrails all drift. A jailbreak that was patched last sprint can silently return after a prompt edit or a model upgrade. Promptfoo maps its plugins directly onto the OWASP LLM Top 10 (owasp:llm) and OWASP Agentic (owasp:agentic) presets, and onto MITRE ATLAS, so the suite tracks recognized risk taxonomies. The core threat addressed here is AML.T0051 — LLM Prompt Injection (MITRE ATLAS): adversarial instructions that override the application's intended behavior. This skill follows the Promptfoo red-team docs (https://www.promptfoo.dev/docs/red-team/) and DeepTeam docs (https://www.trydeepteam.com/docs/getting-started), and aligns to NIST AI RMF MANAGE-4.1 (post-deployment monitoring and feedback to manage AI risk).

When to Use

  • When you need continuous, automated red-teaming of an LLM app in CI/CD rather than one-off manual tests.
  • When you want to enforce a security gate: block merges that introduce or reintroduce jailbreak/injection vulnerabilities.
  • When mapping coverage to OWASP LLM Top 10 / OWASP Agentic / MITRE ATLAS for compliance reporting.
  • When comparing the security posture of two models or prompt versions side by side.
  • When tracking vulnerability regression over time across releases.

Prerequisites

  • Node.js 18+ (Promptfoo is distributed via npm) and Python 3.9+ (for DeepTeam).
  • Install Promptfoo and DeepTeam:
    npm install -g promptfoo            # or: npx promptfoo@latest
    pip install -U deepteam
  • API access/credentials for the target LLM endpoint (and a grader model, e.g. an OpenAI key) exposed as environment variables.
  • A CI/CD platform (GitHub Actions, GitLab CI) with secret storage.
  • Authorization to test the target application.

Objectives

  • Scaffold a Promptfoo red-team config targeting your LLM app.
  • Enable OWASP LLM Top 10 and OWASP Agentic plugin presets plus jailbreak/injection strategies.
  • Run the suite locally and interpret the per-plugin pass/fail report.
  • Add DeepTeam as a second engine for programmatic, research-backed attacks.
  • Integrate both into CI/CD so builds fail on new vulnerabilities.
  • Generate shareable HTML/PDF security reports per run.

MITRE ATT&CK Mapping

ID Name (MITRE ATLAS) Tactic
AML.T0051 LLM Prompt Injection Initial Access / Persistence (LLM)
AML.T0051.000 Direct (Prompt Injection) LLM Attack
AML.T0051.001 Indirect (Prompt Injection) LLM Attack
AML.T0054 LLM Jailbreak Privilege Escalation / Defense Evasion (LLM)

Workflow

1. Scaffold the red-team configuration

Initialize an interactive config; it writes promptfooconfig.yaml where targets, plugins, and strategies live.

promptfoo redteam init
# choose your target type (HTTP endpoint, openai:..., anthropic:..., custom provider)

2. Define targets, OWASP presets, and attack strategies

Edit promptfooconfig.yaml. The purpose grounds attack generation; plugins are adversarial input generators; strategies are delivery techniques (jailbreak/injection wrappers).

# promptfooconfig.yaml
targets:
  - id: https://api.example.com/chat        # your app endpoint
    label: support-bot
 
redteam:
  purpose: |
    A customer-support assistant for an e-commerce site. Must never reveal
    system prompts, leak PII, or perform actions outside order support.
  numTests: 10
  plugins:
    - owasp:llm          # OWASP LLM Top 10 preset
    - owasp:agentic      # OWASP Agentic threats preset
    - id: pii:direct
      numTests: 15
    - prompt-extraction  # system-prompt leakage
    - harmful
  strategies:
    - id: jailbreak              # iterative single-turn jailbreak
    - id: jailbreak:composite    # stacked jailbreak techniques
    - id: crescendo              # multi-turn escalation
    - id: prompt-injection       # injection wrapper

3. Run the suite and view the report

redteam run combines generation + evaluation; then open the interactive report.

promptfoo redteam run
promptfoo redteam report            # launches the web report (pass/fail per plugin)

Each row shows the plugin (mapped to OWASP/ATLAS), the strategy, the attack prompt, the model's response, and the grader's verdict. The attack success rate per plugin is your headline metric — track it per release.

4. Add DeepTeam for programmatic, research-backed attacks

Use DeepTeam to cover additional vulnerabilities/attacks and to script bespoke suites in Python.

# deepteam_suite.py
from deepteam import red_team
from deepteam.vulnerabilities import Bias, PIILeakage
from deepteam.attacks.single_turn import PromptInjection
 
def model_callback(prompt: str) -> str:
    # call your application's LLM endpoint here and return the text response
    return call_my_app(prompt)
 
red_team(
    model_callback=model_callback,
    vulnerabilities=[Bias(types=["race"]), PIILeakage(types=["api_and_database_access"])],
    attacks=[PromptInjection()],
)

DeepTeam can also be driven from a YAML config:

deepteam run config.yaml

5. Gate the build in CI/CD (GitHub Actions)

Fail the pipeline when red-team assertions fail. Promptfoo returns a non-zero exit code on failures, which blocks the merge.

# .github/workflows/llm-redteam.yml
name: LLM Red Team
on: [pull_request]
jobs:
  redteam:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20' }
      - run: npm install -g promptfoo
      - name: Run red team (fails build on new vulns)
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
        run: promptfoo redteam run --no-progress-bar
      - name: Export machine-readable results
        if: always()
        run: promptfoo redteam report --output results.json
      - uses: actions/upload-artifact@v4
        if: always()
        with: { name: redteam-report, path: results.json }

6. Track regressions over time

Persist results.json per run and compare attack-success-rate per plugin between releases. A rising rate for any OWASP LLM category is a regression to triage before release. Promptfoo's --filter-failing lets you re-run only previously failing cases to confirm a fix.

promptfoo redteam run --filter-failing results.json

Tools and Resources

Resource Link
Promptfoo red-team docs https://www.promptfoo.dev/docs/red-team/
Promptfoo red-team configuration https://www.promptfoo.dev/docs/red-team/configuration/
Promptfoo CI/CD integration https://www.promptfoo.dev/docs/integrations/ci-cd/
Promptfoo MITRE ATLAS mapping https://www.promptfoo.dev/docs/red-team/mitre-atlas/
DeepTeam (Confident AI) https://github.com/confident-ai/deepteam
DeepTeam docs https://www.trydeepteam.com/docs/getting-started
OWASP Top 10 for LLM Applications https://genai.owasp.org/

Plugin / Strategy Reference

Promptfoo item Type Maps to
owasp:llm preset OWASP LLM Top 10 suite
owasp:agentic preset OWASP Agentic threats
prompt-extraction plugin LLM07 system-prompt leakage
pii:direct plugin LLM06 sensitive-info disclosure
harmful plugin harmful content generation
jailbreak / jailbreak:composite strategy AML.T0054 LLM jailbreak
crescendo strategy multi-turn jailbreak
prompt-injection strategy AML.T0051 prompt injection

Validation Criteria

  • promptfooconfig.yaml created with target, owasp:llm, and owasp:agentic plugins.
  • Jailbreak and prompt-injection strategies enabled.
  • promptfoo redteam run executes and produces a per-plugin pass/fail report.
  • DeepTeam suite runs against the same target via model_callback.
  • CI/CD job fails the build on new red-team failures (non-zero exit).
  • results.json artifact archived per run for regression tracking.
  • Attack-success-rate per OWASP category trended across releases.
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.1 KB

Promptfoo / DeepTeam — Command & Config Reference

Install

Tool Command
Promptfoo (global) npm install -g promptfoo
Promptfoo (no install) npx promptfoo@latest redteam run
DeepTeam pip install -U deepteam

Promptfoo Red-Team CLI

Command Purpose
promptfoo redteam init Scaffold an interactive red-team config
promptfoo redteam generate Generate adversarial test cases only
promptfoo redteam run Generate + evaluate (combined)
promptfoo redteam eval Evaluate existing generated tests
promptfoo redteam report Open/export the results report
promptfoo redteam plugins List available plugins
promptfoo redteam strategies List available strategies

Useful flags: --no-progress-bar (CI), --output results.json, --filter-failing <file>, -c <config>.

Promptfoo Config Keys (redteam: block)

Key Purpose
purpose Application description; grounds attack generation
numTests Tests generated per plugin
plugins Adversarial generators (e.g. owasp:llm, owasp:agentic, pii:direct, prompt-extraction, harmful)
strategies Delivery techniques (jailbreak, jailbreak:composite, crescendo, prompt-injection)
targets Endpoints/models under test

DeepTeam Python API

Import Purpose
from deepteam import red_team Run a red-team assessment
from deepteam.vulnerabilities import Bias, PIILeakage Vulnerability definitions (50+)
from deepteam.attacks.single_turn import PromptInjection Single-turn attack methods
red_team(model_callback=..., vulnerabilities=[...], attacks=[...]) Execute the suite

DeepTeam CLI

Command Purpose
deepteam run config.yaml Run red teaming from a YAML config

External References

standards.md1.5 KB

Standards and References — Continuous LLM Red Teaming with Promptfoo

MITRE ATLAS Techniques

ID Name Tactic Rationale
AML.T0051 LLM Prompt Injection LLM Attack Core class of attack generated and regression-tested by the suite.
AML.T0051.000 Direct Prompt Injection LLM Attack Injection delivered directly in the user prompt.
AML.T0051.001 Indirect Prompt Injection LLM Attack Injection delivered via retrieved/external content.
AML.T0054 LLM Jailbreak LLM Attack Jailbreak strategies (jailbreak, composite, crescendo) test guardrail bypass.

NIST AI RMF

ID Function Rationale
MANAGE-4.1 Post-deployment monitoring plans are implemented; AI risks are tracked and managed Continuous CI/CD red-teaming is the post-deployment monitoring control for LLM risk.

Official Resources

Frameworks Tracked

  • OWASP LLM Top 10 (owasp:llm preset)
  • OWASP Agentic threats (owasp:agentic preset)
  • MITRE ATLAS (Promptfoo ATLAS mapping)

Scripts 1

agent.py5.0 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
# For authorized LLM security testing only. Run adversarial probes against apps
# you own or are permitted to test.
"""Continuous LLM red-teaming helper for Promptfoo + DeepTeam.

Subcommands:
  scaffold  - Write a starter promptfooconfig.yaml with OWASP LLM/Agentic presets.
  run       - Invoke `promptfoo redteam run` (subprocess) and capture exit status.
  parse     - Parse a Promptfoo results.json and report attack-success-rate per
              plugin, returning non-zero if any rate exceeds a threshold (CI gate).
"""
import argparse
import json
import shutil
import subprocess
import sys

STARTER_CONFIG = """\
targets:
  - id: {target}
    label: target-under-test

redteam:
  purpose: |
    {purpose}
  numTests: {num_tests}
  plugins:
    - owasp:llm
    - owasp:agentic
    - prompt-extraction
    - id: pii:direct
      numTests: 15
    - harmful
  strategies:
    - id: jailbreak
    - id: jailbreak:composite
    - id: crescendo
    - id: prompt-injection
"""


def cmd_scaffold(args):
    cfg = STARTER_CONFIG.format(target=args.target, purpose=args.purpose,
                                num_tests=args.num_tests)
    with open(args.out, "w", encoding="utf-8") as fh:
        fh.write(cfg)
    print(f"[+] wrote {args.out} targeting {args.target}")
    print("    next: promptfoo redteam run -c " + args.out)
    return 0


def cmd_run(args):
    if shutil.which("promptfoo") is None:
        print("[!] promptfoo not found. Install: npm install -g promptfoo", file=sys.stderr)
        return 1
    cmd = ["promptfoo", "redteam", "run", "-c", args.config, "--no-progress-bar"]
    if args.output:
        cmd += ["--output", args.output]
    print("[*] " + " ".join(cmd))
    try:
        proc = subprocess.run(cmd, timeout=args.timeout)
    except FileNotFoundError:
        print("[!] promptfoo binary missing", file=sys.stderr)
        return 1
    except subprocess.TimeoutExpired:
        print("[!] red-team run timed out", file=sys.stderr)
        return 1
    print(f"[+] promptfoo exit code: {proc.returncode}")
    return proc.returncode


def _walk_results(data):
    """Yield (plugin, passed:bool) from a Promptfoo results.json structure."""
    results = data.get("results", data)
    rows = results.get("results") if isinstance(results, dict) else results
    if not isinstance(rows, list):
        return
    for r in rows:
        meta = r.get("metadata", {}) or {}
        plugin = (meta.get("pluginId") or meta.get("plugin")
                  or r.get("vars", {}).get("pluginId") or "unknown")
        passed = bool(r.get("success", r.get("pass", False)))
        yield plugin, passed


def cmd_parse(args):
    with open(args.results, "r", encoding="utf-8") as fh:
        data = json.load(fh)
    agg = {}
    for plugin, passed in _walk_results(data):
        a = agg.setdefault(plugin, {"total": 0, "attack_success": 0})
        a["total"] += 1
        if not passed:                       # a failed assertion == successful attack
            a["attack_success"] += 1
    if not agg:
        print("[!] no parseable results found", file=sys.stderr)
        return 1

    print(f"{'PLUGIN':<32} {'TESTS':>6} {'ASR':>7}")
    breached = []
    for plugin, a in sorted(agg.items(), key=lambda kv: -kv[1]["attack_success"]):
        asr = a["attack_success"] / a["total"] if a["total"] else 0.0
        flag = "  <== over threshold" if asr > args.max_asr else ""
        if asr > args.max_asr:
            breached.append(plugin)
        print(f"{plugin:<32} {a['total']:>6} {asr:>6.0%}{flag}")

    if breached:
        print(f"\n[FAIL] {len(breached)} plugin(s) exceed ASR {args.max_asr:.0%}: "
              + ", ".join(breached))
        return 2
    print(f"\n[PASS] all plugins within ASR threshold {args.max_asr:.0%}")
    return 0


def main():
    p = argparse.ArgumentParser(description="Promptfoo/DeepTeam red-team CI helper")
    sub = p.add_subparsers(dest="cmd", required=True)

    s = sub.add_parser("scaffold", help="write a starter promptfooconfig.yaml")
    s.add_argument("--target", required=True, help="endpoint or provider id, e.g. openai:gpt-4o")
    s.add_argument("--purpose", default="Describe the application under test.")
    s.add_argument("--num-tests", type=int, default=10)
    s.add_argument("--out", default="promptfooconfig.yaml")
    s.set_defaults(func=cmd_scaffold)

    r = sub.add_parser("run", help="invoke promptfoo redteam run")
    r.add_argument("--config", default="promptfooconfig.yaml")
    r.add_argument("--output", help="write results.json")
    r.add_argument("--timeout", type=int, default=3600)
    r.set_defaults(func=cmd_run)

    pa = sub.add_parser("parse", help="parse results.json and gate on attack-success-rate")
    pa.add_argument("--results", required=True)
    pa.add_argument("--max-asr", type=float, default=0.0,
                    help="max allowed attack-success-rate per plugin (0.0 = zero tolerance)")
    pa.set_defaults(func=cmd_parse)

    args = p.parse_args()
    sys.exit(args.func(args))


if __name__ == "__main__":
    main()
Keep exploring