Skip to main content

Plugins

Plugins are the core unit of work in BlackRainbow. Each plugin generates attack sequences, then grades the results after execution. The plugin system uses a decorator-based registry for zero-config discovery.

How Plugins Work

Every plugin implements two methods:

  1. generate(context): Takes an EngagementContext, returns a list of AttackSequence objects. Each sequence defines what tools to run, with what arguments, against what target type.
  2. grade(sequence, result): Takes the original sequence and the execution result, returns a GradeResult with a score, pass/fail, evidence, and metadata.

Optionally, plugins can implement get_model_prompt(context) to generate prompts for the BlackRainbow AI model (used in ZeroBoy, planned).

Plugin Registry

Plugins register themselves via the @register_plugin decorator:

from blackrainbow.plugins import register_plugin
from blackrainbow.plugins.base import PluginBase

@register_plugin
class MyPlugin(PluginBase):
plugin_id = "my-plugin"
description = "Does something useful"
colors = ["red"]
...

At import time, the decorator adds the class to PLUGIN_REGISTRY, a module-level dict keyed by plugin_id. The CLI triggers imports via _import_builtins() to keep startup fast.

Registry functions:

FunctionDescription
register_plugin(cls)Decorator. Registers a plugin class by its plugin_id.
get_plugin(plugin_id, config=None)Instantiates a plugin by ID. Raises ValueError if not found.
list_plugins()Returns metadata for all registered plugins.

Built-in: Recon Plugin

The recon plugin (plugin_id: "recon") handles network and service enumeration using nmap. It generates three scanning phases and grades results by parsing nmap output.

Three-Phase Scanning

Phase 1: Full TCP port scan

nmap -sC -sV -p- -oX /tmp/br-nmap-full-<host>.xml <host>
  • Scans all 65,535 TCP ports
  • Service and version detection (-sV)
  • Default NSE scripts (-sC)
  • XML output for structured parsing
  • Timeout: 600 seconds
  • MITRE: T1046 (Network Service Discovery)

Phase 2: Quick top-ports scan

nmap -sC -sV -oX /tmp/br-nmap-quick-<host>.xml <host>
  • Default top 1000 ports
  • Fast initial results while full scan runs
  • Timeout: 120 seconds
  • MITRE: T1046

Phase 3: Vulnerability scripts

nmap --script vuln -oX /tmp/br-nmap-vuln-<host>.xml <host>
  • NSE vulnerability detection scripts
  • Timeout: 300 seconds
  • MITRE: T1595.002 (Active Scanning: Vulnerability Scanning)

Output Parsing

The recon plugin parses both XML and text nmap output:

XML parsing (parse_nmap_services): Extracts <port> elements from nmap XML. For each open port, captures port number, protocol, service name, product, and version.

Text fallback: Parses lines matching PORT/tcp open SERVICE VERSION format. Used when XML output is not available.

Vulnerability parsing (parse_nmap_vulns): Scans XML <script> elements for "VULNERABLE" markers. Falls back to text parsing for |_ prefixed NSE output blocks.

Grading Logic

The recon grader scores on a 0.0 to 1.0 scale:

DiscoveryScore Added
Services found (any)+0.5
Vulnerabilities found (any)+0.5

A grade passes if score > 0 (at least one service discovered). The grade metadata includes the full services list and vulnerability list, which feeds into service accumulation.

Model Prompt

When ZeroBoy is active, the recon plugin generates a prompt asking the model to suggest targeted enumeration based on discovered services. The prompt requests JSON output with tool, args, looking_for, and MITRE technique for each suggestion.

Color System

Each plugin declares one or more colors that categorize its domain:

ColorDomain
redOffensive operations, exploitation
orangeReconnaissance, enumeration
yellowCredential access, lateral movement
blueDetection, defense
greenOSINT, passive collection
greyDetection engineering
purpleCombined attack + detect

The recon plugin declares ["orange", "red", "green"]. Colors are displayed in the br plugins table and are used for filtering in the training pipeline.

Planned Plugins

PluginDescriptionStatus
exploitationExploit generation and deliveryPlanned
credential-accessPassword attacks, hash crackingPlanned
web-exploitWeb application attacksPlanned

Writing a Custom Plugin

Create a new file in blackrainbow/plugins/ (or any importable location):

"""Custom plugin example."""

from typing import Any, Dict, List

from blackrainbow.plugins import register_plugin
from blackrainbow.plugins.base import (
AttackSequence,
EngagementContext,
ExecutionResult,
GradeResult,
PluginBase,
Severity,
)


@register_plugin
class MyCustomPlugin(PluginBase):
plugin_id = "my-custom"
description = "Custom plugin for specific enumeration"
colors = ["orange"]
default_num_tests = 5
default_severity = Severity.MEDIUM

def generate(self, context: EngagementContext) -> List[AttackSequence]:
host = context.target.get("host", "")
return [
AttackSequence(
plugin_id=self.plugin_id,
description=f"Custom scan of {host}",
steps=[{
"tool": "my-tool",
"args": f"--target {host}",
"timeout": 120,
}],
target_type="network-service",
mitre_techniques=["T1046"],
colors=self.colors,
severity=self.severity,
metadata={"phase": "custom"},
)
]

def grade(self, sequence: AttackSequence, result: ExecutionResult) -> GradeResult:
found_something = "interesting" in result.stdout.lower()
return GradeResult(
plugin_id=self.plugin_id,
passed=found_something,
score=1.0 if found_something else 0.0,
objective_achieved=found_something,
evidence=[result.stdout[:500]] if found_something else [],
mitre_techniques=sequence.mitre_techniques,
reasoning="Found target data" if found_something else "Nothing found",
metadata={},
)

PluginBase Class Reference

AttributeTypeDefaultDescription
plugin_idstr""Unique identifier, used in config and registry
descriptionstr""Human-readable description
colorsList[str][]Color categories
default_num_testsint5Default number of test sequences
default_severitySeverityMEDIUMDefault severity level
MethodRequiredDescription
generate(context)YesGenerate attack sequences
grade(sequence, result)YesGrade execution results
get_model_prompt(context)NoGenerate AI model prompt

The constructor accepts an optional config dict. It reads numTests and severity from config, falling back to class defaults.