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](https://github.com/EvilBit-Labs/opnDossier) |
| Open Issues | 50 |
| Stars / Forks | 15 / 4 |
| 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) |
| Component | Technology |
| CLI Framework | `spf13/cobra` v1.8.0 |
| CLI Enhancement | `charmbracelet/fang` (styled help, errors) |
| Configuration | `spf13/viper` (YAML, env vars, flags) |
| Terminal Styling | `charmbracelet/lipgloss` |
| Markdown Rendering | `charmbracelet/glamour` |
| Markdown Generation | `nao1215/markdown` (programmatic builder) |
| Logging | `charmbracelet/log` (structured) |
| Data Formats | `encoding/xml`, `encoding/json`, `gopkg.in/yaml.v3` |
| Testing | Go `testing` • `sebdah/goldie/v2` (golden files) |
| Task Runner | `just` (Justfile) |
| Release | GoReleaser v2 (multi-platform) |
| 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 ` | Compare two configuration files |
| `config init` | Initialize opnDossier configuration file |
| `config show` | Display current configuration |
| `config validate [file]` | Validate opnDossier config file |
| `completion [shell]` | Generate shell completion scripts |
| `man [dir]` | Generate man pages |
| `version` | Print version information |
(XML DTOs)"] C --> D["ParserFactory
(auto-detect device type)"] D --> E["opnsense/converter.go"] E --> F["common.CommonDevice
(platform-agnostic)"] F --> G["prepareForExport()
(enrichment)"] G --> H["JSON / YAML / Markdown
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. |
| OPNsense Pattern | Go Type | Example |
| Presence-based boolean (`isset()` in PHP) | `BoolFlag` | ``, ``, ``, `` |
| Value-based boolean (`== "1"` in PHP) | `string` | `1`, `1` |
| Presence with value access | `*string` | `` in Source/Destination |
| Container + children | `[]ChildType` in container struct | `VLANs` container with `[]VLAN` slice |
(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 ```mermaid graph TD A["CLI audit command"] --> B["PluginManager"] B --> C["PluginRegistry
(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` interface | `internal/compliance/interfaces.go` | Standardized contract: Name, Version, Description, RunChecks, GetControls |
| `PluginRegistry` | `internal/audit/plugin.go` | Thread-safe registry with duplicate detection |
| `PluginManager` | `internal/audit/plugin_manager.go` | Plugin 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) |
| 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) |
| Component | Location | Purpose |
| `MarkdownBuilder` | `internal/converter/builder/` | Fluent API for markdown construction |
| `SectionWriter` interface | `internal/converter/builder/writer.go` | Streaming `io.Writer` support |
| `Generator` interface | `internal/converter/` | Format-agnostic generation contract |
| `HybridGenerator` | `internal/converter/hybrid_generator.go` | Multi-format dispatcher (markdown, JSON, YAML) |
| Formatters | `internal/converter/formatters/` | Format-specific serialization logic |
| Workflow | File | Purpose |
| CI | `ci.yml` | Lint (golangci-lint), format (gofumpt), tests, CodeQL, Grype |
| Benchmarks | `benchmarks.yml` | Performance benchmarks (non-blocking, `continue-on-error: true`) |
| Release | `release.yml` | GoReleaser: multi-platform builds, changelogs, signing |
| SBOM | `sbom.yml` | Software Bill of Materials generation |
| Scorecard | `scorecard.yml` | OSSF Scorecard security assessment |
| Docs | `docs.yml` | MkDocs Material documentation build |
| Copilot Setup | `copilot-setup-steps.yml` | GitHub Copilot workspace configuration |
| # | 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 |
| # | 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 |
| 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. |
| 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 |
| 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) |
| # | 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 |
| # | 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 sanitize | 2026-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 |
Critical patterns every contributor must know
**1. XML Presence Detection**: Use `*string` not `string` for optional XML elements. `` and absent elements both produce `""` with `string`. **2. Struct Shallow Copy**: `normalized := *cfg` shares slice backing arrays. Always deep-copy slices before mutation. **3. Deterministic Output**: ALL lists from maps must be sorted (`slices.Sorted(maps.Keys())`) before rendering, comparing, or serializing. **4. Statistics Sync**: Adding a field to `common.Statistics` requires updating 3 places (struct, `computeStatistics()`, `computeTotalConfigItems()`). **5. cfgparser/Schema Sync**: Renaming schema fields requires updating `cfgparser/xml.go` switch cases. **6. Dual Validator Sync**: `internal/processor/validate.go` and `internal/validator/opnsense.go` maintain parallel whitelists that must stay in sync. **7. Mutex Non-Reentrancy**: Go's `sync.RWMutex` is NOT reentrant. Use internal `*Unsafe()` helpers for methods called while holding a lock. **8. Plugin Findings**: `RunChecks()` may return nil (normalize to empty slice) and results may be cached (always `slices.Clone()` before mutating). **9. No Templates**: All report generation is programmatic via `builder.MarkdownBuilder`. The `internal/templates/` directory does not exist. **10. ****`//nolint:`**** Placement**: Must be on a SEPARATE LINE above the call (inline gets stripped by gofumpt).| Package | Production LOC | Test LOC | Ratio | Responsibility |
| `converter` | 5,074 | 6,227 | 1.23:1 | Multi-format export (MD/JSON/YAML), streaming I/O, enrichment |
| `model` | 4,179 | 6,890 | 1.65:1 | CommonDevice, OPNsense converter, factory, re-export layer |
| `processor` | 3,468 | 4,521 | 1.30:1 | Report generation, compliance audit, statistics |
| `diff` | 2,854 | 3,412 | 1.19:1 | Config diffing, security change analysis, formatters |
| `schema` | 2,479 | 2,088 | 0.84:1 | OPNsense XML struct definitions |
| `sanitizer` | 1,539 | 1,702 | 1.11:1 | Sensitive data redaction, field/value pattern matching |
| `validator` | 1,194 | 1,406 | 1.18:1 | Semantic validation (whitelists, constraints) |
| `config` | 1,043 | 873 | 0.84:1 | Viper-based config management |
| `display` | 926 | 1,040 | 1.12:1 | Terminal rendering with lipgloss, wrapping |
| `audit` | 900 | 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](http://AGENTS.md), architecture docs, Dosu knowledge base, GitHub issues |
| Approval Status | Draft |