Documents
CLI Command Responsibilities
CLI Command Responsibilities
Type
Topic
Status
Published
Created
Mar 22, 2026
Updated
Apr 2, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

CLI Command Responsibilities#

The opnDossier CLI architecture defines five distinct top-level commands for processing firewall configuration files (OPNsense and pfSense config.xml), each with a single, well-defined responsibility. The commands follow a consistent architectural pattern built on the Cobra framework, sharing common parsing infrastructure while delivering specialized output formats and functionality. This separation of concerns ensures stable API surfaces and prevents feature drift across multiple entry points.

The five commands are: display (styled terminal view), convert (multi-format export), audit (security/compliance reports), sanitize (sensitive data redaction), and validate (structural validation). Each command processes firewall configuration files with a distinct input/output contract, from terminal visualization to format conversion, security auditing, data sanitization, and validation.

The audit command was introduced in PR #454 (March 2026) as a dedicated first-class entry point, replacing the previous convert --audit-mode workflow. This architectural decision established clearer command boundaries, enabled specialized output handling including glamour-rendered terminal output, and provided concurrent multi-file processing capabilities. The evolution reflects a broader design philosophy prioritizing single-responsibility commands with stable, predictable interfaces. The command originally supported three modes (standard/blue/red), but standard mode was removed in March 2026 as it duplicated the neutral documentation functionality already provided by the convert command without adding audit-specific value.

Command Overview and Input/Output Contracts#

Each of the five commands accepts firewall configuration files (OPNsense and pfSense config.xml) as input but serves a distinct purpose in the configuration processing workflow. The commands automatically detect device type by inspecting the XML root element (<opnsense> vs <pfsense>), though users can override detection via the global --device-type flag (values: opnsense, pfsense). The following sections detail their individual responsibilities, input/output contracts, and implementation characteristics.

display: Styled Terminal View#

The display command renders firewall configurations as styled markdown in the terminal using the Glamour library. Unlike other commands, display is exclusively for terminal visualization and does not support file output.

Input/Output Contract:

  • Input: Single config.xml file
  • Output: Glamour-rendered markdown to stdout only (no file export)

Key Flags:

  • --theme (auto/dark/light/none) — controls Glamour rendering style and color scheme
  • Shared flags: --section, --wrap, --comprehensive, --include-tunables, --redact

Implementation Details: The command parses config.xml via parser.NewFactory(), which automatically detects device type by inspecting the root element (<opnsense> vs <pfsense>) and dispatches to the appropriate parser registered via init() functions in pkg/parser/opnsense and pkg/parser/pfsense. The command generates markdown through the builder and renders via Glamour to stdout. Options are wired through buildDisplayOptions() following the standard precedence chain: CLI flags > config file > defaults.

convert: Multi-Format Configuration Export#

The convert command is the primary workhorse for transforming config.xml into structured documentation formats. As the largest CLI file at 803 lines, it supports five output formats with extensive configuration options.

Input/Output Contract:

  • Input: One or more config.xml files
  • Output: stdout or file in markdown/JSON/YAML/text/HTML format (default: markdown)

Key Flags:

  • --format, -f — output format selection (markdown, json, yaml, text, html)
  • --output, -o — output file path
  • --force — overwrite without prompt
  • Shared flags: --section, --wrap, --comprehensive, --include-tunables, --redact

Implementation Details: The command processes files concurrently using a semaphore to parallelize multi-file operations. Each file is parsed via the shared factory pattern, then builds options with buildConversionOptions(), and dispatches to format handlers via converter.DefaultRegistry. The command includes overwrite protection with user prompts unless --force is specified, preventing accidental data loss.

audit: Security and Compliance Reports#

The audit command provides dedicated security audit and compliance checking as a first-class CLI entry point. Introduced in PR #454, it separates security concerns from general format conversion and supports concurrent multi-file processing with specialized output handling.

Input/Output Contract:

  • Input: One or more config.xml files (concurrent multi-file support)
  • Output: stdout (glamour-styled markdown) or auto-named files

Key Flags:

  • --mode (blue/red, default: blue) — audit mode selection for different security perspectives
  • --plugins (stig,sans,firewall; blue mode only) — compliance framework plugins
  • --plugin-dir — directory for dynamic .so plugin loading
  • Shared output flags: --format, --output, --wrap, --section, --comprehensive, etc.

Implementation Details: The command processes multiple files concurrently with semaphore limiting (runtime.NumCPU()) to maximize throughput on multi-core systems. The implementation is split across two files: audit.go handles command definition, flags, validation, and core execution logic, while audit_output.go manages output emission and path derivation.

Multi-file naming uses tilde-based escaping to create unique output filenames: prod/site-a/config.xmlprod_site-a_config-audit.md. Markdown output to stdout is glamour-rendered for styled terminal display, while file output and non-markdown formats are written raw. PreRunE validation enforces strict flag constraints: valid audit modes, valid plugin names, --plugins only with --mode blue, and rejects --output when processing multiple files to prevent output path collisions.

sanitize: Sensitive Data Redaction#

The sanitize command redacts sensitive data from config.xml files with referential integrity, ensuring that identical original values are consistently mapped to the same redacted values throughout the document. Unlike other commands, sanitize operates directly on XML without parsing to the internal CommonDevice model.

Input/Output Contract:

  • Input: Single config.xml file
  • Output: Sanitized XML to stdout or file

Key Flags:

  • --mode, -m (aggressive/moderate/minimal, default: moderate) — controls redaction level
  • --output, -o — output file path
  • --mapping — JSON mapping file path for documenting original→redacted value mappings
  • --force — overwrite without prompt

Sanitization Modes:

  • Aggressive: passwords, keys, certificates, all IP addresses, MAC addresses, emails, hostnames, usernames, domains
  • Moderate (default): passwords, keys, authserver LDAP values, public IP addresses, MAC addresses, emails (preserves private IPs and hostnames outside system/authserver for network topology analysis)
  • Minimal: passwords, secrets, API keys, PSKs, private keys, SSH keys, and sensitive system/authserver LDAP values (preserves most network information for troubleshooting, excluding authserver hosts)

Implementation Details: The command does NOT parse to CommonDevice; instead, it operates on raw XML via internal/sanitizer/ for direct manipulation. Referential integrity is maintained through consistent value mapping: if 10.0.1.5 is redacted to 192.0.2.1, all occurrences of 10.0.1.5 throughout the document receive the same redacted value. The command can optionally write a mapping file documenting all original→redacted transformations for reverse lookup during analysis.

validate: Structural and Semantic Validation#

The validate command validates firewall configuration structure and content, performing both structural XML validation and semantic field-level checks. Unlike other commands with extensive flag sets, validate has minimal command-specific flags, primarily relying on global configuration.

Input/Output Contract:

  • Input: One or more config.xml files
  • Output: Validation pass/fail status with structured error messages (human-readable or JSON)

Key Flags:

  • --json-output — outputs validation errors in JSON format for machine consumption (validate command only)
  • Global flags: --verbose, --quiet, --device-type

Implementation Details: The command processes multiple files concurrently and calls the factory with validateMode=true as the fourth parameter to trigger strict ParseAndValidate instead of the standard Parse method. This enables both structural and semantic validation checks.

On validation failure, the command outputs structured error messages in the format ❌ config.xml: validation failed with 3 errors: hostname is required... followed by detailed field-level error descriptions. With --json-output, errors are emitted in JSON format for machine consumption. Success produces a simple ✅ config.xml: Valid message. Exit codes are atomically tracked across goroutines via updateMaxExitCode to ensure proper CLI behavior in CI/CD pipelines.

The --json-output flag is scoped exclusively to the validate command because no other commands need JSON-formatted validation output. This scoping decision prevents flag pollution on other commands where the flag would have no effect.

Evolution from convert --audit-mode to Dedicated audit Command#

The audit functionality in opnDossier underwent significant architectural evolution, moving from an integrated feature within the convert command to a standalone, purpose-built command. This transition exemplifies the project's commitment to single-responsibility design and stable command interfaces.

Historical Context#

The audit feature underwent three major iterations:

  1. PR #175 (January 2026): Stubbed audit mode code was removed from cmd/convert.go entirely and deferred to v2.1.

  2. PR #224 (February 2026): Audit mode was re-integrated into the CLI, wired into the convert command with flags: --audit-mode, --audit-blackhat, --audit-plugins. Added cmd/audit_handler.go with handleAuditMode().

  3. PR #454 (March 2026): Audit flags removed from convert; dedicated opndossier audit command introduced as the canonical entry point with three modes: standard (neutral documentation), blue (defensive audit), and red (attack surface analysis).

  4. PR #465 (March 2026): Standard mode removed from the audit command as it duplicated convert functionality without audit-specific value. Neutral, comprehensive documentation is now exclusively handled by the convert command, leaving audit focused on security-specific perspectives (blue and red modes).

PR #454: Key Changes and Rationale#

PR #454 introduced the opndossier audit command to cleanly separate command responsibilities. The architectural rationale centered on four core principles:

  • Command responsibility clarity: Each command should have a single, well-defined purpose. Security auditing is functionally distinct from format conversion.
  • Stable API surface: Audit-specific flags (--mode, --plugins) don't belong on a general-purpose conversion command where they create conceptual confusion.
  • Consistent output handling: A dedicated command enables specialized output behaviors like glamour-rendered terminal output and intelligent multi-file naming.
  • Prevention of drift: Centralizing audit concerns in a single entry point prevents the dual-maintenance burden of keeping two command paths synchronized.

Flags Removed from convert:

  • --audit-mode → migrated as --mode to the audit command
  • --audit-plugins → migrated as --plugins to the audit command
  • --audit-blackhatremoved entirely as non-functional placeholder
  • --plugin-dir → migrated to the audit command

Importantly, these flags were never released on the convert command's public surface, making the transition seamless without breaking changes to existing users. The addSharedAuditFlags() function was removed from cmd/shared_flags.go as part of this cleanup.

--blackhat Flag Removal#

The --audit-blackhat flag and associated addSnarkyCommentary() function were removed in PR #454 because they constituted "a placeholder that set two metadata keys with no real functionality." The flag had been added in PR #224 with the intention of supporting "red team attack surface analysis with blackhat commentary," but was never meaningfully implemented beyond setting metadata. Rather than carrying forward non-functional code, the project opted for clean removal.

Comparison: audit vs convert --audit-mode#

The following table highlights the key differences between the dedicated audit command and the legacy convert --audit-mode approach:

Aspectopndossier auditconvert --audit-mode
Flag names--mode, --plugins (unprefixed)--audit-mode, --audit-plugins (prefixed)
Multi-file processingConcurrent with auto-named outputsSequential with explicit output paths
Terminal outputGlamour-styled markdown for enhanced readabilityRaw markdown output
IntroducedPR #454 (March 2026)PR #224 (February 2026)
StatusCanonical entry point for audit workflowsLegacy approach, superseded but retained for compatibility

Outstanding Work: TODO #457#

Internal audit plumbing was intentionally retained in PR #454 for follow-up cleanup. Issue #457 tracks the removal of dead audit code from the convert command implementation, including sharedAuditMode package-level variables and audit-related parameters in generateOutputByFormat that remain in the codebase even though the public CLI surface no longer exposes those flags. This technical debt represents internal implementation details that no longer serve a purpose after the command separation.

Shared Architectural Patterns#

Despite their distinct responsibilities, all five commands share common architectural patterns that promote code reuse, consistency, and maintainability. These shared patterns span configuration parsing, flag management, options construction, and error handling.

Config.xml Processing Pipeline#

All commands except sanitize use the same parsing approach through the factory pattern:

parser.NewFactory(cfgparser.NewXMLParser()).CreateDevice(ctx, file, resolveDeviceType(), validateMode)

The factory automatically detects device type by inspecting the XML root element (<opnsense> vs <pfsense>) and dispatches to the appropriate parser registered via init() functions. cmd/root.go imports both parsers via blank imports for automatic registration:

_ "github.com/EvilBit-Labs/opnDossier/pkg/parser/opnsense" // self-registers OPNsense parser
_ "github.com/EvilBit-Labs/opnDossier/pkg/parser/pfsense" // self-registers pfSense parser

The fourth parameter (validateMode) is true only for the validate command, which triggers strict ParseAndValidate instead of the standard Parse method. This unified parsing approach ensures consistent behavior across commands while allowing validation-specific logic where needed. The sanitize command diverges from this pattern by operating directly on raw XML for performance and referential integrity requirements.

Shared Flag Infrastructure#

Shared flags are defined in cmd/shared_flags.go as package-level variables that multiple commands can reference. These include sharedIncludeTunables, sharedComprehensive, sharedRedact, sharedSections, sharedWrapWidth, and other configuration options that apply across different output modes.

The deviceTypeDescriptions map in cmd/shared_flags.go includes entries for both "opnsense" and "pfsense" to provide shell completion suggestions for the global --device-type flag.

validateOutputFlags() in cmd/shared_flags.go centralizes format, wrap, and section validation logic for the convert and audit commands. This function:

  • Validates requested format against converter.DefaultRegistry to ensure the format handler exists
  • Enforces --wrap/--no-wrap mutual exclusivity to prevent conflicting configuration
  • Warns when --section filtering is used with JSON/YAML formats where section filtering may not apply semantically

This centralized validation prevents duplicate logic and ensures consistent flag behavior across commands.

Options Builder Pattern#

Command handlers transform flags into typed converter.Options structures via builder functions that encapsulate all configuration state. These builders follow a consistent precedence chain: CLI flags > config file values > defaults. This pattern ensures that user intentions expressed via CLI flags always take precedence, while configuration files provide convenient defaults.

Known Issue: Issue #412 documented a critical bug where buildDisplayOptions() failed to wire sharedIncludeTunables, causing the --include-tunables flag to be silently ignored. This issue exemplifies the importance of comprehensive flag wiring. The fix added opt.IncludeTunables = sharedIncludeTunables to complete the wiring, and established a testing pattern to prevent similar regressions.

Audit Handler Flow#

handleAuditMode() in cmd/audit_handler.go orchestrates audit execution for both the dedicated audit command and the legacy convert --audit-mode workflow. This shared handler ensures consistent audit behavior regardless of entry point:

  1. Creates PluginManager instance, calls SetPluginDir() if custom plugin directory specified
  2. Calls InitializePlugins() to register built-in plugins (STIG, SANS, Firewall) and load dynamic .so plugins
  3. Checks GetLoadResult() and warns on failed dynamic plugin loads (non-fatal)
  4. Runs PluginManager.RunComplianceAudit() to execute compliance checks, producing an audit.Report
  5. Maps audit report to common.ComplianceResults via mapAuditReportToComplianceResults()
  6. Assigns compliance results to device.ComplianceChecks on a shallow device copy
  7. Generates output through format handlers via the standard generator pipeline

This centralized handler prevents code duplication and ensures that audit behavior remains consistent across both entry points.

Error Handling Patterns#

All commands follow consistent error handling patterns that distinguish between fatal errors and recoverable warnings. Commands use fmt.Errorf("context: %w", err) wrapping throughout to provide error context while preserving the underlying error for inspection. PreRunE hooks catch invalid flag combinations before execution begins, providing fast feedback to users.

Fatal errors (e.g., file not found, parse failures) halt processing immediately. Conversion warnings (non-fatal issues like empty firewall rule fields, missing NAT data, gateway issues) are logged via structured logging and processing continues. These warnings can be suppressed with the --quiet flag when running in automated environments.

Rendering Behavior Differences#

The five commands exhibit different rendering behaviors based on their intended use cases. Commands optimized for terminal display use Glamour for styled markdown, while file-oriented commands write raw content:

ContextRendering
display command → stdoutAlways glamour-rendered, themed markdown (user-configurable theme)
audit command → stdout, markdown formatGlamour-rendered styled markdown for enhanced terminal readability
audit command → stdout, non-markdown formatRaw output (JSON/YAML/text/HTML written directly without styling)
audit command → file outputRaw content (no glamour styling, optimized for file storage)
convert command → stdoutRaw markdown output (no glamour styling)
convert command → fileRaw output via format handler (all formats)

This distinction ensures that terminal-focused workflows benefit from enhanced readability, while file-based workflows produce clean, parseable output suitable for automation and version control.

Relevant Code Files#

The following table catalogs the primary implementation files for the CLI command architecture, including line counts and core responsibilities:

FileRoleLines
cmd/audit.goAudit command implementation: flags, PreRunE validation, runAudit, generateAuditOutput~400
cmd/audit_output.goAudit output handling: emitAuditResult, deriveAuditOutputPath, path escaping~200
cmd/audit_handler.goShared audit orchestration: handleAuditMode(), mapAuditReportToComplianceResults()~150
cmd/convert.goConvert command: buildConversionOptions(), format dispatch, overwrite protection803
cmd/display.goDisplay command: buildDisplayOptions(), glamour rendering, theme selection~330
cmd/validate.goValidate command: ParseAndValidate invocation, structured error formatting~220
cmd/sanitize.goSanitize command: sanitization mode selection, mapping file generation~320
cmd/shared_flags.goShared infrastructure: flag variables, validateOutputFlags(), helper functions~400
cmd/root.goRoot command: global flags, parser registration via blank import376
  • Parser Factory Pattern: Registration and dispatch system for automatic device type detection via XML root element inspection (<opnsense> vs <pfsense>) and multi-device parser registration
  • Converter Registry: Format handler registration and dispatch mechanism
  • Compliance Plugin System: Pluggable security and compliance checks with STIG, SANS, and firewall frameworks
  • CommonDevice Model: Shared internal representation of parsed firewall configurations (OPNsense and pfSense)
  • Glamour Rendering: Terminal styling for markdown output in display and audit commands
CLI Command Responsibilities | Dosu