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 id | Category |
|---|---|
bias-demographic | Bias / demographic |
competitor-mention | Brand / competitor leakage |
contract-overstep | Policy: overstepping authority |
hallucination | Hallucination |
harmful-content | Harmful content (umbrella) |
harmful-hate-speech | Harmful: hate speech |
harmful-misinformation | Harmful: misinformation |
harmful-self-harm | Harmful: self-harm |
harmful-violence | Harmful: violence |
hate | Hate |
pii-api-query | PII: API query exposure |
pii-direct-ns | PII: direct, non-sensitive |
pii-direct | PII: direct |
pii-session | PII: session-bound |
prompt-injection | Prompt injection |
violence | Violence |
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 id | Description |
|---|---|
basic | Identity — no mutation; the raw plugin probe. |
base64 | Base64-encode the malicious instruction. |
crescendo | Multi-turn escalation framing. |
leetspeak | Substitute letters with leet equivalents. |
prompt-injection | Wrap the probe in a prompt-injection envelope. |
roleplay | Frame 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 theRedTeamRunstruct.sarif— SARIF 2.1.0 for ingestion into security dashboards. Every finding becomes a SARIFresult; severity maps to SARIFlevel(critical/high→error,medium→warning,low→note).
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_testsand seed to get reproducible probe sets across runs.