Skip to content

Plugin System

lib/plugin_loader.sh is the runtime extension mechanism for netvuln-tool. It loads community (core) and enterprise plugins with automatic discovery, dependency ordering, and non-fatal hook execution. It is Bash 3.2+ compatible (no associative arrays or namerefs): plugin state is kept as newline-delimited key-value strings and hook registrations as indexed arrays.

Design: Stub-First Safety

The loader follows a stub-first pattern. Before any plugin discovery runs, no-op stubs are defined for the enterprise entry points the pipeline calls unconditionally, so the pipeline can never crash on a missing plugin function:

bash
map_compliance()        { return 0; }   # report phase
build_topology()        { return 0; }   # report phase
generate_exec_summary() { return 0; }   # config-gated
nv_submit_session()     { return 1; }   # portal upload (1 = not available)

When a plugin loads successfully, its plugin_init overrides these stubs with real implementations. If the plugin is absent or its tier is not licensed, the harmless stub stands in.

Plugin Structure

Plugins live under scripts/plugins/ in two tiers:

scripts/plugins/
├── core/           # Community plugins (ship with the OSS repo)
│   ├── scan-diff/
│   │   └── plugin.sh
│   └── alert-notifier/
│       └── plugin.sh
└── enterprise/     # License-gated plugins (compliance, topology, portal, ...)
    └── <name>/
        └── plugin.sh

The open-source repository ships two core plugins (scan-diff and alert-notifier); the enterprise/ directory is where license-gated plugins are installed. A third location can be added with the NV_PLUGIN_DIR environment variable.

Each plugin directory contains a plugin.sh manifest declaring metadata variables and a plugin_init function. plugin_init receives the plugin's own directory as $1:

bash
PLUGIN_NAME="scan-diff"
PLUGIN_VERSION="1.0.0"
PLUGIN_EDITION="community"           # or "enterprise"
PLUGIN_DESCRIPTION="What this plugin does"
PLUGIN_DEPENDS=""                    # comma-separated dependency names
PLUGIN_HOOKS="pre-report"           # hook points this plugin uses

plugin_init() {
    local plugin_dir="$1"
    # Source additional files, set up state, register hooks.
    nv_register_hook "pre-report" "_nv_plugin_scan_diff"
    return 0
}

Hook Points

Plugins register functions to run at defined points via nv_register_hook. The pipeline fires them with nv_run_hooks:

Hook PointWhen It Fires
post-discoveryAfter all discovery modules complete
post-enumerationAfter all enumeration modules complete
post-assessmentAfter vulnerability assessment completes
pre-reportBefore report generation begins
post-reportAfter report generation completes
post-uploadAfter session upload to the portal
post-pipelineAfter the entire pipeline finishes

Hook execution is non-fatal: a failing hook is logged and the pipeline continues. Multiple plugins can register for the same hook point, and they run in dependency-resolved order. Arguments passed to nv_run_hooks are forwarded to each registered function (for example, post-report receives the session JSON path, report file, and config path).

Dependency Ordering

Plugins declare dependencies with PLUGIN_DEPENDS (comma-separated names). The loader topologically sorts plugins so dependencies load before dependents. A missing dependency skips the dependent plugin with a warning, and circular dependencies are broken by visited-node detection.

API

bash
# Source the plugin loader (double-source guarded)
source lib/plugin_loader.sh

# Discover and load all plugins from core/, enterprise/, and NV_PLUGIN_DIR
nv_load_plugins

# Run every function registered at a hook point (args forwarded to each)
nv_run_hooks "post-report" "$session_json" "$report_file" "$config"

# Register a hook (typically inside plugin_init)
nv_register_hook "post-report" "my_function_name"

The plugin registry records each plugin as loaded or skipped, queryable through the loader's metadata store.

Apache-2.0 licensed (appliance subtree proprietary)