Red Teaming

kanros ships a red-team subsystem that generates adversarial probes, sends them at one or more providers, and judges the responses. The probes are deterministic given a seed, and the entire pipeline is offline unless you actually call kanros redteam run.

The architecture lives in crates/kanros-redteam/:

 Config (red_team: block)
     │
     ▼
 Registry  ──►  RedTeamPlugin  ──►  synthesize(n, seed) → Vec<Probe>
     │              │                judge(probe, response) → Option<Finding>
     │              ▼
     └──►  Strategy  ──►  apply(probe) → Probe
              │
              ▼
 runner::run_red_team(...) → RedTeamRun { findings, … }

Two roles

A plugin is a category of attack. It owns the prompt templates, synthesises probes, and judges responses for that category. A strategy is a transformation applied on top of a plugin's probe — base64-encoding the malicious instruction, leetspeak, role-play framing, and so on.

Plugins and strategies compose: for every cell in the red-team matrix, kanros runs prompt = strategy(plugin.synthesize()) against each configured provider.

Bundled plugins

The following plugins are registered by Registry::with_defaults() and correspond to source files in crates/kanros-redteam/src/plugins/:

Plugin idCategory
bias-demographicBias / demographic
competitor-mentionBrand / competitor leakage
contract-overstepPolicy: overstepping authority
hallucinationHallucination
harmful-contentHarmful content (umbrella)
harmful-hate-speechHarmful: hate speech
harmful-misinformationHarmful: misinformation
harmful-self-harmHarmful: self-harm
harmful-violenceHarmful: violence
hateHate
pii-api-queryPII: API query exposure
pii-direct-nsPII: direct, non-sensitive
pii-directPII: direct
pii-sessionPII: session-bound
prompt-injectionPrompt injection
violenceViolence

Every plugin is Send + Sync + Debug and deterministic: given the same seed, synthesize(n, seed) returns the same Vec<Probe> byte-for-byte.

Bundled strategies

Strategies live in crates/kanros-redteam/src/strategies/:

Strategy idDescription
basicIdentity — no mutation; the raw plugin probe.
base64Base64-encode the malicious instruction.
crescendoMulti-turn escalation framing.
leetspeakSubstitute letters with leet equivalents.
prompt-injectionWrap the probe in a prompt-injection envelope.
roleplayFrame the probe as a role-play scenario.

Configuring a red-team run

Add a red_team: block to your config:

red_team:
  plugins:
    - prompt-injection
    - harmful-content
    - pii-direct
  strategies:
    - basic
    - base64
    - leetspeak
  num_tests: 5

Then either generate probes for offline review:

kanros redteam generate --config kanros.yaml --format yaml > probes.yaml

…or actually run them against the configured providers:

kanros redteam run --config kanros.yaml --format sarif > findings.sarif

Outputs

kanros redteam run supports three output formats:

  • terminal — human-readable table grouped by severity.
  • json — pretty JSON of the RedTeamRun struct.
  • sarif — SARIF 2.1.0 for ingestion into security dashboards. Every finding becomes a SARIF result; severity maps to SARIF level (critical/higherror, mediumwarning, lownote).

Exit codes follow the same convention as the eval runner: 0 when no findings, 1 when one or more findings are produced.

Adding plugins

The RedTeamPlugin trait is small enough to implement in tens of lines:

use kanros_redteam::{Probe, RedTeamPlugin, Category, Severity, Finding};

#[derive(Debug)]
struct MyPlugin;

impl RedTeamPlugin for MyPlugin {
    fn id(&self) -> &'static str { "my-plugin" }
    fn category(&self) -> Category { Category::PromptInjection }
    fn default_severity(&self) -> Severity { Severity::Medium }

    fn synthesize(&self, n: u32, seed: u64) -> Vec<Probe> { /* … */ vec![] }
    fn judge(&self, probe: &Probe, response: &str) -> Option<Finding> { None }
}

Register it in your fork of kanros-redteam or via a future plugin host (planned for 0.2). Until then, custom plugins require a workspace fork or a contribution to the upstream registry.

Notes

  • The bundled judges are intentionally crude heuristics. Treat findings as triage signals, not policy decisions. A model-graded judge is on the roadmap.
  • Strategies are pure functions of a probe; they do not call any provider.
  • The generator is deterministic. CI can pin the num_tests and seed to get reproducible probe sets across runs.