Project Overview#
Attribute Value Language Go 1.26 (toolchain go1.26.0) License Apache-2.0 Latest Release v1.2.1 (2026-02-12) Repository EvilBit-Labs/opnDossier Open Issues 50 Stars / Forks 15 / 4 opnDossier is a CLI tool for converting OPNsense firewall configuration files (config.xml) into human-readable documentation, structured exports (JSON/YAML), and compliance audit reports. It is designed for offline-first operation in airgapped environments with zero external dependencies.
Codebase Metrics#
Metric Value Production code (internal/) ~28,200 lines Test code (internal/) ~55,500 lines CLI layer (cmd/) ~4,500 lines production + ~4,300 lines test Internal packages 24 Schema structs (XML DTOs) ~96 types across 14 files CommonDevice model types ~87 types across 15 files Golden test files 27 CI workflows 7 (benchmarks, ci, copilot-setup, docs, release, sbom, scorecard) Test-to-production ratio ~1.9:1 (tests significantly outnumber production code)
Architecture Overview#
Design Principles#
Offline-First : Zero external dependencies, complete airgap compatibility
Operator-Focused : Built for network administrators and security professionals
Framework-First : Leverages established Go libraries (Cobra, Charm ecosystem)
Security-First : No telemetry, input validation, secure processing, XXE prevention
Multi-Device Extensible : Clean separation between XML DTOs and platform-agnostic domain model
Technology Stack#
Component Technology CLI Framework spf13/cobra v1.8.0CLI Enhancement charmbracelet/fang (styled help, errors)Configuration spf13/viper (YAML, env vars, flags)Terminal Styling charmbracelet/lipglossMarkdown Rendering charmbracelet/glamourMarkdown Generation nao1215/markdown (programmatic builder)Logging charmbracelet/log (structured)Data Formats encoding/xml, encoding/json, gopkg.in/yaml.v3Testing Go testing • sebdah/goldie/v2 (golden files) Task Runner just (Justfile)Release GoReleaser v2 (multi-platform)
CLI Commands#
Command Purpose convert [file ...]Parse config.xml and export to markdown, JSON, or YAML display [file]Render config as styled markdown in terminal validate [file ...]Validate OPNsense configuration structure sanitize [file]Redact sensitive data from config files diff <old> <new>Compare two configuration files config initInitialize opnDossier configuration file config showDisplay current configuration config validate [file]Validate opnDossier config file completion [shell]Generate shell completion scripts man [dir]Generate man pages versionPrint version information
Two-Stage Parsing Architecture#
The core data pipeline separates XML-specific concerns from domain logic, enabling multi-device support through a pluggable registry system. The Factory now uses a DeviceParserRegistry for dispatch instead of hardcoded switch statements.
Copy graph TD
A["config.xml"] --> B["cfgparser/xml.go"]
B --> C["schema.OpnSenseDocument<br/>(XML DTOs)"]
C --> D["ParserFactory<br/>(auto-detect device type)"]
D --> E["opnsense/converter.go"]
E --> F["common.CommonDevice<br/>(platform-agnostic)"]
F --> G["prepareForExport()<br/>(enrichment)"]
G --> H["JSON / YAML / Markdown<br/>Output"]
Stage 1: XML Parsing#
Package : internal/cfgparser/
The streaming XML parser reads OPNsense config.xml into schema.OpnSenseDocument DTOs. Key features:
Security protections : XXE prevention, XML bomb protection
Charset conversion : UTF-8, US-ASCII, ISO-8859-1, Windows-1252
Streaming processing : Memory-efficient handling of large configurations
Switch-case routing : Each top-level XML element dispatched by tag name in xml.go
Stage 2: Domain Conversion#
Package : internal/model/opnsense/
The converter transforms XML DTOs into the platform-agnostic CommonDevice model:
converter.go -- core conversion and system fields
converter_network.go -- bridges, PPPs, GIFs, GREs, LAGGs, VIPs, interface groups
converter_security.go -- certificates, CAs, packages
converter_services.go -- DHCP, DNS, VPN, routing, etc.
ParserFactory#
File : internal/model/factory.go
ParserFactory.CreateDevice() auto-detects the device type by inspecting the root XML element. The --device-type flag bypasses auto-detection. Currently only OPNsense is implemented; the interface is designed for extension.
Data Model Layer#
Three-Layer Architecture#
Layer Package Purpose XML DTOs internal/schema/Mirrors OPNsense config.xml structure with xml:"" tags. ~96 struct types. Domain Model internal/model/common/Platform-agnostic CommonDevice with JSON/YAML tags. ~87 types. No XML tags. Re-export Seam internal/model/~93 type aliases bridging schema and common. Temporary; targeted for removal in v1.3.0.
CommonDevice Structure#
The CommonDevice model organizes data into three categories:
Core Configuration (concrete types, populated during parsing):
System, Interfaces, VLANs, Bridges, LAGGs, VirtualIPs, GIFs, GREs, PPPs
Firewall Rules, NAT (inbound + outbound), VPN (OpenVPN, WireGuard, IPsec)
Routing (gateways, static routes), Services (DHCP, DNS, NTP, SNMP)
Security (certificates, CAs, IDS/IPS), Users, Groups, Packages
Optional Features (pointer types for non-universal features):
IDS/IPS (Suricata), Monit, Netflow/IPFIX, Traffic shaper, Captive portal, Cron, Kea DHCP
Enrichment Fields (pointer types, populated by prepareForExport()):
*Statistics, *Analysis, *SecurityAssessment, *PerformanceMetrics, *ComplianceChecks
XML Type Patterns#
OPNsense Pattern Go Type Example Presence-based boolean (isset() in PHP) BoolFlag<disabled/>, <log/>, <not/>, <quick/>Value-based boolean (== "1" in PHP) string<enable>1</enable>, <blockpriv>1</blockpriv>Presence with value access *string<any/> in Source/DestinationContainer + children []ChildType in container structVLANs container with []VLAN slice
Enrichment Pipeline#
File : internal/converter/enrichment.go
The prepareForExport() function is the single gate for all JSON/YAML exports. It enriches a shallow copy of CommonDevice with computed analytics:
Copy graph TD
A["CommonDevice<br/>(from converter)"] --> B["prepareForExport()"]
B --> C["computeStatistics()"]
B --> D["computeAnalysis()"]
B --> E["computeSecurityAssessment()"]
B --> F["computePerformanceMetrics()"]
C --> G["Enriched CommonDevice"]
D --> G
E --> G
F --> G
G -->|"if redact=true"| H["redactSensitiveFields()"]
H --> I["Final Export"]
G -->|"if redact=false"| I
Key design constraints :
Immutability : Creates shallow copies with selective deep-copying before redaction
Circular dependency avoidance : Cannot import internal/processor -- analysis logic is mirrored, not shared. This is a deliberate architectural trade-off (package independence over code deduplication)
Redaction flow : CLI flag -> Options.Redact -> prepareForExport(data, redact) -> conditional redaction
Statistics synchronization : Adding fields to common.Statistics requires updating three places (struct definition, computeStatistics(), and computeTotalConfigItems())
Compliance Plugin System#
Architecture#
Copy graph TD
A["CLI audit command"] --> B["PluginManager"]
B --> C["PluginRegistry<br/>(thread-safe, sync.RWMutex)"]
C --> D["Firewall Plugin"]
C --> E["SANS Plugin"]
C --> F["STIG Plugin"]
D --> G["RunChecks(*CommonDevice)"]
E --> G
F --> G
G --> H["[]Finding"]
H --> I["Compliance Report"]
Core Components#
Component File Purpose compliance.Plugin interfaceinternal/compliance/interfaces.goStandardized contract: Name, Version, Description, RunChecks, GetControls PluginRegistryinternal/audit/plugin.goThread-safe registry with duplicate detection PluginManagerinternal/audit/plugin_manager.goPlugin lifecycle: load, validate, execute, report Firewall plugin internal/plugins/firewall/OPNsense-specific firewall checks SANS plugin internal/plugins/sans/SANS security best practices (SANS-XXX controls) STIG plugin internal/plugins/stig/STIG compliance controls (STIG-V-XXXXXX)
Audit Report Modes#
Mode Audience Focus Status Standard Operations Neutral configuration documentation Implemented Blue Blue Team Defensive analysis, findings, recommendations Planned (issue #281) Red Red Team Attack surface enumeration, pivot analysis Planned (issue #281)
Known Audit Issues (Tracked)#
#310 : calculateSummary severity counting is non-functional -- Finding.Type is always "compliance", not a severity level
#309 : No panic recovery around plugin RunChecks() calls -- dynamic plugins can crash entire audit
#311 : Dynamic plugin load failures are silently swallowed
Report Generation System#
Reports are generated programmatically using builder.MarkdownBuilder (backed by nao1215/markdown). There is no template system -- all markdown is constructed via Go method calls for type safety, performance, and testability.
Builder Architecture#
Component Location Purpose MarkdownBuilderinternal/converter/builder/Fluent API for markdown construction SectionWriter interfaceinternal/converter/builder/writer.goStreaming io.Writer support Generator interfaceinternal/converter/Format-agnostic generation contract HybridGeneratorinternal/converter/hybrid_generator.goMulti-format dispatcher (markdown, JSON, YAML) Formatters internal/converter/formatters/Format-specific serialization logic
Sanitizer & Redaction#
The sanitizer uses a rule-based engine for sensitive data redaction:
Field-pattern rules : Match by field name (e.g., password, secret, otp_seed)
Value-detector rules : Match by value content (e.g., IP addresses with IsIP detector)
Deterministic mapping : Fresh RuleEngine creates fresh Mapper -- first private IP always maps to 10.0.0.1, first hostname to host-001.example.com
--redact** flag**: Opt-in redaction for convert and display commands
Configuration Diff Engine#
Package : internal/diff/
The diff command compares two OPNsense configurations and outputs a structured change report. Uses comparison functions with nil-safe patterns (both-nil -> nil, one-nil -> added/removed) and slices.Equal() for slice fields.
Package Dependency Structure#
The internal packages follow a layered architecture with clear dependency direction:
Copy graph TD
CMD["cmd/"] --> MODEL["model/"]
CMD --> PROCESSOR["processor/"]
CMD --> CONVERTER["converter/"]
CMD --> CFGPARSER["cfgparser/"]
CMD --> VALIDATOR["validator/"]
CMD --> DISPLAY["display/"]
CMD --> AUDIT["audit/"]
CMD --> CONFIG["config/"]
CMD --> SANITIZER["sanitizer/"]
CMD --> DIFF["diff/"]
CMD --> PROGRESS["progress/"]
CMD --> EXPORT["export/"]
PROCESSOR --> MODEL
CONVERTER --> MODEL
CONVERTER --> BUILDER["converter/builder/"]
AUDIT --> COMPLIANCE["compliance/"]
AUDIT --> PLUGINS["plugins/"]
PLUGINS --> COMPLIANCE
PLUGINS --> MODEL
CFGPARSER --> SCHEMA["schema/"]
MODEL --> SCHEMA
MODEL --> COMMON["model/common/"]
MODEL --> OPNSENSE["model/opnsense/"]
OPNSENSE --> SCHEMA
OPNSENSE --> COMMON
Key constraint : converter/ and processor/ both depend on model/common/ but cannot depend on each other (circular dependency). Analysis logic is deliberately mirrored in both packages.
CI/CD Pipeline#
Workflow File Purpose CI ci.ymlLint (golangci-lint), format (gofumpt), tests, CodeQL, govulncheck, Trivy Benchmarks benchmarks.ymlPerformance benchmarks (non-blocking, continue-on-error: true) Release release.ymlGoReleaser: multi-platform builds, changelogs, signing SBOM sbom.ymlSoftware Bill of Materials generation Scorecard scorecard.ymlOSSF Scorecard security assessment Docs docs.ymlMkDocs Material documentation build Copilot Setup copilot-setup-steps.ymlGitHub Copilot workspace configuration
Quality Gate#
just ci-check runs the full CI-equivalent locally: pre-commit hooks + gofumpt formatting check + golangci-lint + all tests. Tasks are not complete until ci-check passes.
Open Issues & Roadmap#
Priority: Critical (Architecture)#
# Title Category 322 Move audit report rendering from cmd to converter/builder layer refactor 321 Extract shared analysis package to eliminate enrichment mirror refactor 320 Split report.go into focused files refactor 319 Split opnsense.go validator into domain-specific files refactor 318 Split builder.go into domain-specific files refactor
Priority: High#
# Title Category 325 Add FormatRegistry to replace scattered format routing refactor 324 Extract Severity type to shared package refactor 323 Split ReportBuilder interface (18 methods) into focused interfaces refactor 310 Severity breakdown missing in audit reports bug 309 Add panic recovery around plugin RunChecks() enhancement 286 Replace O(n^2) duplicate rule detection with O(n) hash-based performance 282 Eliminate re-export layer (93 type aliases) refactor 280 Unify Finding types across compliance, processor, audit refactor 281 Disable blue/red audit modes until full implementation enhancement 154 Complete template system removal for v2.0 refactor 157 Create shared evilbitlabs-network-model Go module architecture
Public API Roadmap#
Milestone Issue Scope v1.3.0 #301 Move types to pkg/: pkg/model/, pkg/schema/opnsense/, pkg/parser/, pkg/parser/opnsense/. Eliminates re-export layer (~93 aliases, ~110 files need import updates). v2.0.0 #302 Parser registry: replace hardcoded switch in ParserFactory with Register() pattern. External device types via init() functions.
Milestones#
Milestone Progress Focus v1.3.0 18 closed / 27 open UX improvements, documentation, alternative output formats, public pkg/ API v1.4.0 0 closed / 3 open Extract common model libraries into reusable packages v2.0.0 17 closed / 14 open Major architectural changes: programmatic generation, API changes, parser registry v2.1.0 0 closed / 4 open Platform expansion: pfSense, Cisco ASA, Fortinet multi-vendor support
Multi-Device Support Roadmap#
Device Issue Status OPNsense -- Implemented (current) pfSense #197 Planned (v1.3.0+, community-requested) Cisco ASA #198 Planned (post-v2.0.0, enterprise focus) Fortinet FortiGate #199 Planned (post-v2.0.0)
v2.0 Feature Epics#
# Feature Labels 213 Web UI with Local Server Mode premium, v2.0 211 TUI: Interactive Terminal Interface blue-team, v2.0 212 File System Watch Mode for Continuous Monitoring v2.0 209 SARIF Export for CI/CD Security Integration premium, compliance 208 SIEM Export Formats (CEF/LEEF/JSONL) security, v2.0 207 Professional PDF Report Generation premium, v2.0 205 Custom Rule Engine for Organization Policies compliance, v2.0 204 PCI-DSS Requirement 1 Firewall Checks compliance, v2.0 201 Configuration Drift Detection & Baseline Management v2.0
Recent Development Activity#
Most recent closed issues (as of March 2026):
# Title Closed 316 Implement opt-in --redact flag for convert/display 2026-02-26 296 Implement firewall compliance check helpers 2026-02-23 295 Restore semantic validation for CommonDevice 2026-02-23 294 Add nil guards for nested WireGuard structs 2026-02-21 287 Prevent shared backing array mutations in normalize() 2026-02-26 284 Populate unpopulated CommonDevice fields (Bridges, PPPs, GIFs, etc.) 2026-02-26 272 Additional tags for sanitize command (Secrets & Topology) 2026-02-28 271 otp_seed not redacted by sanitize2026-02-20 266 Compliance findings not properly aggregated by plugin 2026-02-23 263 Architecture review: tech debt across model, enrichment, display, logging 2026-02-20
Known Technical Debt#
Architectural Constraints#
Circular dependency : converter/enrichment.go and processor/ both need analysis logic but cannot import each other. Analysis is mirrored in both packages. Issue #321 targets extracting a shared internal/analysis/ package.
Re-export layer : 93 type aliases in internal/model/ re-export types from internal/schema/ and internal/model/common/. Temporary seam for multi-device migration; removal planned in v1.3.0 (issue #282).
ReportBuilder interface : Currently 18 methods in a single interface. Issue #323 targets splitting into focused interfaces.
Sequential plugin execution : No concurrent plugin execution; potential bottleneck for large configurations with many compliance checks.
Code Quality Items#
Global state : Package-level variables in cmd/ (required by Cobra's flag binding) and audit/plugin.go
Context underutilization : context.Context not fully leveraged in long-running operations
Finding type duplication : Three similar Finding types across compliance, processor, and audit packages (issue #280)
Severity type scattered : No shared severity enum; each package defines its own (issue #324)
Schema Gaps#
~40+ string fields should be BoolFlag type
~9 struct{} fields should be BoolFlag
Source/Destination missing Address, Not, and Port (Source) fields (issue #255)
Full inventory documented in docs/development/xml-structure-research.md
Developer Gotchas#
Package Breakdown by Size#
Package Production LOC Test LOC Ratio Responsibility converter5,074 6,227 1.23:1 Multi-format export (MD/JSON/YAML), streaming I/O, enrichment model4,179 6,890 1.65:1 CommonDevice, OPNsense converter, factory, re-export layer processor3,468 4,521 1.30:1 Report generation, compliance audit, statistics diff2,854 3,412 1.19:1 Config diffing, security change analysis, formatters schema2,479 2,088 0.84:1 OPNsense XML struct definitions sanitizer1,539 1,702 1.11:1 Sensitive data redaction, field/value pattern matching validator1,194 1,406 1.18:1 Semantic validation (whitelists, constraints) config1,043 873 0.84:1 Viper-based config management display926 1,040 1.12:1 Terminal rendering with lipgloss, wrapping audit900 945 1.05:1 Plugin manager, compliance check orchestration
Field Value Document Version 1.0 Created Date 2026-03-07 Generated From Codebase analysis, AGENTS.md , architecture docs, Dosu knowledge base, GitHub issues Approval Status Draft