Module System¶
This document covers the module registry, descriptors, families, and canonical import paths.
ModuleRegistry¶
The registry is a singleton accessed via get_registry(). It stores all registered ModuleDescriptor instances and provides lookup and query methods.
Methods¶
register(descriptor) -> None¶
Register a module. Raises ValueError on duplicate module_id.
from blue_tap.framework.registry import get_registry, ModuleDescriptor, ModuleFamily
registry = get_registry()
registry.register(ModuleDescriptor(
module_id="assessment.my_check",
family=ModuleFamily.ASSESSMENT,
name="My Check",
description="Example check",
protocols=("Classic",),
requires=("adapter", "classic_target"),
destructive=False,
requires_pairing=False,
schema_prefix="blue_tap.my_check.result",
has_report_adapter=False,
entry_point="blue_tap.modules.assessment.my_check:MyCheckModule",
))
get(module_id) -> ModuleDescriptor¶
Lookup by ID. Raises KeyError if not found.
desc = registry.get("assessment.vuln_scanner")
print(desc.name) # "Vulnerability Scanner"
print(desc.entry_point) # "blue_tap.modules.assessment.vuln_scanner:VulnScannerModule"
print(desc.destructive) # False
try_get(module_id) -> ModuleDescriptor | None¶
Lookup by ID. Returns None if not found. Use this when you're not sure the module exists.
list_all() -> list[ModuleDescriptor]¶
All modules sorted by module_id.
all_modules = registry.list_all()
print(f"Total modules: {len(all_modules)}")
for m in all_modules[:5]:
print(f" {m.module_id}: {m.name}")
list_family(family) -> list[ModuleDescriptor]¶
All modules in a family, sorted.
from blue_tap.framework.registry import ModuleFamily
assessment_modules = registry.list_family(ModuleFamily.ASSESSMENT)
print(f"Assessment modules: {len(assessment_modules)}")
for m in assessment_modules:
print(f" {m.module_id}: {m.name} ({'destructive' if m.destructive else 'safe'})")
list_families() -> list[ModuleFamily]¶
All families that have registered modules.
families = registry.list_families()
for fam in families:
count = len(registry.list_family(fam))
print(f" {fam.value}: {count} modules")
find_by_protocol(protocol) -> list[ModuleDescriptor]¶
Modules supporting a given protocol string.
ble_modules = registry.find_by_protocol("BLE")
print(f"BLE-capable modules: {len(ble_modules)}")
for m in ble_modules:
print(f" {m.module_id}: protocols={m.protocols}")
find_destructive() -> list[ModuleDescriptor]¶
All modules where destructive=True.
dangerous = registry.find_destructive()
print(f"Destructive modules: {len(dangerous)}")
for m in dangerous:
print(f" {m.module_id}: {m.name}")
load_plugins() -> list[str]¶
Load entry-point plugins; returns list of registered module_ids. See Plugin Entry Points for details.
ModuleDescriptor¶
A frozen dataclass that captures all metadata about a module. Defined in blue_tap.framework.registry.descriptors.
Fields¶
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
module_id |
str |
Yes | Family-prefixed dotted id, regex: ^[a-z0-9_]+(\.[a-z0-9_]+)+$ (e.g. assessment.vuln_scanner; dotted hierarchies like assessment.cve.knob are accepted). Also required by make_execution() and build_run_envelope() |
|
family |
ModuleFamily |
Yes | Enum value (see below) | |
name |
str |
Yes | Human-readable name (must be non-empty) | |
description |
str |
Yes | One-line purpose | |
protocols |
tuple[str, ...] |
Yes | e.g. ("Classic", "BLE", "L2CAP") |
|
requires |
tuple[str, ...] |
Yes | e.g. ("adapter", "classic_target") |
|
destructive |
bool |
Yes | Whether the module modifies target state | |
requires_pairing |
bool |
Yes | Whether pairing is mandatory | |
schema_prefix |
str |
Yes | e.g. "blue_tap.vulnscan.result" |
|
has_report_adapter |
bool |
Yes | Whether a report adapter exists for this module | |
entry_point |
str |
Yes | e.g. "blue_tap.modules.assessment.vuln_scanner:VulnScannerModule" |
|
internal |
bool |
No | False |
Internal-only flag (hidden from external API) |
report_adapter_path |
str \| None |
No | None |
Dotted path to a plugin ReportAdapter class, e.g. "my_plugin.adapters:MyAdapter" |
category |
str \| None |
No | None |
Sub-category within a family (e.g. "pairing", "l2cap", "ble") |
references |
tuple[str, ...] |
No | () |
External references (CVEs, RFCs, specifications) |
Validation (__post_init__)¶
The descriptor validates itself on construction:
module_idmust match^[a-z0-9_]+(\.[a-z0-9_]+)+$familymust be aModuleFamilyenum instancemodule_idmust start with{family.value}.namemust be non-emptyprotocolsmust be atuplerequiresmust be atuple
Any violation raises ValueError immediately.
ModuleFamily¶
An enum in blue_tap.framework.registry.families:
class ModuleFamily(str, Enum):
DISCOVERY = "discovery"
RECONNAISSANCE = "reconnaissance"
ASSESSMENT = "assessment"
EXPLOITATION = "exploitation"
POST_EXPLOITATION = "post_exploitation"
FUZZING = "fuzzing"
Canonical Outcomes per Family¶
FAMILY_OUTCOMES (in blue_tap.framework.registry.families) is the single source of truth. make_execution() validates module_outcome against this table at construction time and raises ValueError on any unlisted value — there is no separate validator vocabulary.
| Family | Allowed module_outcome values |
|---|---|
DISCOVERY |
observed, merged, correlated, partial, not_applicable |
RECONNAISSANCE |
observed, merged, correlated, partial, not_applicable, unsupported_transport, collector_unavailable, prerequisite_missing, artifact_collected, hidden_surface_detected, no_relevant_traffic, undetermined, partial_observation, auth_required, not_found, not_connectable, timeout, no_results |
ASSESSMENT |
confirmed, not_detected, inconclusive, pairing_required, not_applicable |
EXPLOITATION |
success, confirmed, unresponsive, recovered, aborted, not_applicable |
POST_EXPLOITATION |
extracted, connected, streamed, transferred, partial, completed, failed, aborted, not_applicable |
FUZZING |
crash_found, crash_detected, timeout, corpus_grown, no_findings, completed, degraded, aborted, pairing_required, reproduced, not_applicable |
HARDWARE |
completed, installed, hooks_active, hooks_partial, not_loaded, prerequisite_missing, spoofed, rejected, restored, method_unavailable, not_applicable |
Registration Methods¶
1. Explicit Registration in __init__.py¶
The standard approach. Each family's __init__.py registers descriptors at import time:
from blue_tap.framework.registry import get_registry, ModuleDescriptor, ModuleFamily
_registry = get_registry()
_registry.register(ModuleDescriptor(
module_id="assessment.vuln_scanner",
family=ModuleFamily.ASSESSMENT,
name="Vulnerability Scanner",
description="CVE and non-CVE vulnerability assessment with structured evidence",
protocols=("Classic", "BLE", "L2CAP", "SDP", "GATT", "RFCOMM", "BNEP", "HID", "SMP"),
requires=("adapter", "classic_target"),
destructive=False,
requires_pairing=False,
schema_prefix="blue_tap.vulnscan.result",
has_report_adapter=True,
entry_point="blue_tap.modules.assessment.vuln_scanner:VulnScannerModule",
))
2. Auto-registration via Module.__init_subclass__()¶
Assessment check files (in modules/assessment/checks/) use Module subclasses that auto-register on class creation. The parent __init__.py imports these modules to trigger registration.
The flow works like this:
graph TD
A["Python imports<br/>modules/assessment/__init__.py"] --> B["__init__.py imports<br/>checks/cve_checks_l2cap.py"]
B --> C["Class definition triggers<br/>__init_subclass__()"]
C --> D["__init_subclass__() calls<br/>get_registry().register()"]
D --> E["ModuleDescriptor stored<br/>in singleton registry"]
style A fill:#1a3a5c,stroke:#2980b9,color:#fff
style E fill:#2d5016,stroke:#4a8c2a,color:#fff
This means that simply importing the family package registers all its modules. No explicit register() call is needed in the check file itself.
3. Plugin Discovery via Entry Points¶
Third-party packages register modules via setuptools entry points. See Plugin Entry Points.
Canonical Import Paths¶
| What you need | Import from |
|---|---|
EvidenceRecord, ArtifactRef |
blue_tap.framework.contracts.result_schema |
build_run_envelope, make_execution, make_evidence, make_artifact |
blue_tap.framework.contracts.result_schema |
emit_cli_event |
blue_tap.framework.runtime.cli_events |
ReportAdapter, SectionModel, SectionBlock |
blue_tap.framework.contracts.report_contract |
Envelope builders (build_attack_result, etc.) |
blue_tap.framework.envelopes.<family> or blue_tap.framework.envelopes |
ModuleDescriptor, ModuleFamily, get_registry |
blue_tap.framework.registry |
FAMILY_OUTCOMES |
blue_tap.framework.registry |
| Report adapters | blue_tap.framework.reporting.adapters |
BlockRendererRegistry |
blue_tap.framework.reporting.renderers.registry |
Session, log_command, get_session |
blue_tap.framework.sessions.store |
| Hardware primitives | blue_tap.hardware.<module> |
| Module implementations | blue_tap.modules.<family>.<module> |
| Report generator | blue_tap.interfaces.reporting.generator |
Deprecated Paths
Never import from core/, attack/, recon/, fuzz/, or report/. These packages contain only deprecation notices.
Available Envelope Builders¶
Each family has specialized builders in blue_tap.framework.envelopes:
| Builder | Family | Import |
|---|---|---|
build_scan_result |
discovery | blue_tap.framework.envelopes.scan |
build_recon_result, build_recon_execution |
reconnaissance | blue_tap.framework.envelopes.recon |
build_attack_result |
exploitation | blue_tap.framework.envelopes.attack |
build_audio_result |
post_exploitation | blue_tap.framework.envelopes.audio |
build_data_result |
post_exploitation | blue_tap.framework.envelopes.data |
build_fuzz_result, build_fuzz_campaign_result, build_fuzz_operation_result, build_fuzz_protocol_execution |
fuzzing | blue_tap.framework.envelopes.fuzz |
build_firmware_status_result, build_firmware_dump_result, build_connection_inspect_result, build_firmware_operation_result |
firmware ops | blue_tap.framework.envelopes.firmware |
build_spoof_result |
spoof ops | blue_tap.framework.envelopes.spoof |
All builders return a dict matching the RunEnvelope shape. They handle run ID generation, timestamps, and evidence/execution construction internally.