Commit and Security Standards#
The opnDossier project enforces comprehensive commit message standards and security-first design principles to maintain code quality, traceability, and operational safety. The project uses Conventional Commits format with mandatory scope requirements and DCO sign-off for all contributions. Security practices include multi-layered XML input validation, environment variable-based secrets management, and secure build configurations.
These standards are enforced through pre-commit hooks, CI/CD pipeline automation, and dedicated security testing. The project prioritizes offline-first operation, zero telemetry, and airgap compatibility while maintaining comprehensive supply chain security through SLSA provenance and Cosign signing.
Commit Standards#
Conventional Commits Framework#
The project enforces Conventional Commits specification with the format: type(scope): description
Required format elements:
- Type - Required prefix indicating change category
- Scope - REQUIRED - indicates affected component
- Description - Brief summary in imperative mood
Example: feat(parser): add support for new XML schema
Commit Types#
feat- New features and functionalityfix- Bug fixes and defect resolutiondocs- Documentation changesstyle- Code formatting (no logic changes)refactor- Code restructuring without behavior changesperf- Performance improvementstest- Test additions/modificationsbuild- Build system changesci- CI/CD pipeline changeschore- Maintenance tasks
Commit Scopes#
Scopes are REQUIRED and identify the affected codebase component:
(cli)- Command-line interface and Cobra commands(parser)- XML parsing and cfgparser module(converter)- Data conversion and markdown generation(audit)- Audit engine and compliance features(config)- Configuration management with Viper(display)- Terminal display and formatting(docs)- Documentation and user guides(model)- Data models and schema(plugin)- Compliance plugin system(builder)- Programmatic markdown builder
Commit Message Rules#
Message formatting requirements:
- Use imperative mood ("add feature" not "added feature")
- No period at end of description
- ≤72 characters for subject line
- Capitalize first letter
- Breaking changes: add
!before colon or useBREAKING CHANGE:in footer
Breaking change examples:
feat(api)!: change configuration file format
fix(parser)!: remove deprecated XML attribute support
BREAKING CHANGE: The legacy XML format is no longer supported
DCO Sign-off Requirement#
All commits require DCO (Developer Certificate of Origin) sign-off:
git commit -s -m "feat(parser): add support for new XML schema"
The -s flag adds a Signed-off-by line certifying agreement with the Developer Certificate of Origin.
Commit Validation Enforcement#
Commit message validation is enforced through pre-commit hooks:
# Install pre-commit hooks with commit-msg validation
just install
# Runs: mise install && pre-commit install --hook-type pre-commit --hook-type commit-msg --hook-type pre-push
Manual installation (if needed):
pre-commit install --hook-type pre-commit --hook-type commit-msg --hook-type pre-push
Two-tier hook system:
pre-commithooks (fast, every commit) - formatters, lint config verification, andgolangci-lintwith auto-fixpre-pushhook (slow, once per push) - runs fulljust ci-checksuite includingtest-race(the Go race detector cannot be hosted reliably on GitHub Actions runners, so the pre-push hook is the enforcement point)
git-cliff parses commits using Conventional Commits for changelog generation:
[git]
conventional_commits = true
filter_unconventional = true # Filters non-conventional commits
Security Standards#
Core Security Principles#
The project enforces security-first design principles:
- No hardcoded secrets - Never include API keys, passwords, or sensitive data in code
- Environment variables - Use
OPNDOSSIER_*prefixed environment variables for sensitive configuration - Input validation - Always validate and sanitize XML input files before processing
- Secure defaults - Apply secure configuration defaults by design
- Error message safety - Avoid exposing sensitive information in error messages
- Airgap compatibility - Full functionality in isolated/air-gapped environments
- No telemetry - Zero external data transmission
Input Validation#
Multi-layered XML validation in internal/cfgparser/xml.go:
Size Limiting (prevents XML bombs):
const DefaultMaxInputSize = 10 * 1024 * 1024 // 10MB
type XMLParser struct {
MaxInputSize int64
}
func (p *XMLParser) Parse(ctx context.Context, r io.Reader) (*schema.OpnSenseDocument, error) {
limitedReader := io.LimitReader(r, p.MaxInputSize)
// Process with size limit to prevent memory exhaustion
}
XXE Attack Prevention:
dec := xml.NewDecoder(limitedReader)
dec.Entity = map[string]string{} // Empty entity map prevents XXE attacks
Charset Validation:
func charsetReader(charset string, input io.Reader) (io.Reader, error) {
normalizedCharset := strings.ToLower(strings.TrimSpace(charset))
switch normalizedCharset {
case "us-ascii", "ascii", "utf-8", "utf8", "iso-8859-1":
return input, nil
default:
return nil, fmt.Errorf("unsupported charset: %s", charset)
}
}
Additional validation in internal/validator/opnsense.go validates IP addresses, port ranges, and configuration semantics.
GID/UID Validation Standard#
Unix GID 0 (wheel/root group) and UID 0 (root user) are valid system identifiers. Validator checks must use gid < 0 / uid < 0 to reject only negative values, correctly allowing zero.
Error message requirement: Messages must say "non-negative integer", not "positive integer".
Reference: GOTCHAS.md section 6.1
This standard ensures correct validation of Unix system identifiers throughout the codebase.
Error Handling Without Sensitive Exposure#
Structured error handling in internal/cfgparser/errors.go:
type ParseError struct {
Line int // Line number where error occurred
Column int // Column number where error occurred
Message string // Human-readable message (no sensitive data)
}
func (e *ParseError) Error() string {
return fmt.Sprintf("parse error at line %d, column %d: %s",
e.Line, e.Column, e.Message)
}
// Wraps XML errors without exposing file content
func WrapXMLSyntaxError(err error, elementPath string) error {
var syntaxErr *xml.SyntaxError
if errors.As(err, &syntaxErr) {
message := syntaxErr.Msg
if elementPath != "" {
message = fmt.Sprintf("%s (in element path: %s)", message, elementPath)
}
return &ParseError{Line: syntaxErr.Line, Message: message}
}
return &ParseError{Message: "XML error: " + err.Error()}
}
Data sanitization in internal/sanitizer/sanitizer.go redacts sensitive data:
func (s *Sanitizer) sanitizeCommentContent(content string) string {
words := strings.Fields(content)
for i, word := range words {
should, _ := s.engine.ShouldRedactValue("comment", word)
if should {
words[i] = s.engine.Redact("comment", word)
}
}
return strings.Join(words, " ")
}
Secrets Management#
Environment variable configuration in internal/config/config.go:
// Configuration precedence: CLI flags > env vars > config file > defaults
v.SetEnvPrefix("OPNDOSSIER")
v.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_"))
v.AutomaticEnv()
// Explicit nested key binding for complex configs
nestedEnvBindings := map[string]string{
"display.width": "DISPLAY_WIDTH",
"logging.level": "LOGGING_LEVEL",
"validation.strict": "VALIDATION_STRICT",
}
for key, envSuffix := range nestedEnvBindings {
v.BindEnv(key, "OPNDOSSIER_"+envSuffix)
}
Configuration precedence order:
- CLI flags (highest priority)
- Environment variables (
OPNDOSSIER_*) - Configuration file (
~/.opnDossier.yaml) - Default values (lowest priority)
Sensitive Data Detection#
Pattern detection utilities in internal/sanitizer/patterns.go:
func IsPrivateIP(s string) bool {
ip := net.ParseIP(s)
if ip == nil {
return false
}
// Check IPv4 private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
for _, r := range privateIPRanges {
if r.Contains(ip) {
return true
}
}
return false
}
func LooksLikePassword(fieldName string) bool {
lower := strings.ToLower(fieldName)
passwordKeywords := []string{
"password", "passwd", "pass", "secret", "key", "token",
"credential", "auth", "prv", "private",
}
for _, kw := range passwordKeywords {
if strings.Contains(lower, kw) {
return true
}
}
return false
}
func IsCertificate(s string) bool {
if IsPEM(s) {
return strings.Contains(s, "CERTIFICATE")
}
return IsBase64(s)
}
Dependency Security#
The project implements comprehensive dependency security:
- Minimal dependencies - Reduced attack surface (except for cryptography—never write custom crypto)
- gosec integration - Local security scanning via
just scan - Supply chain security - Go module checksums verify integrity
- SBOM generation - CycloneDX format for transparency
- Continuous vulnerability scanning -
.github/workflows/security.ymlruns govulncheck (Go vulnerability database), CodeQL (semantic analysis), and Trivy (filesystem scan + misconfiguration detection) on push/PR and weekly
Operational Security Testing#
Automated security guarantees in internal/security_test.go:
func TestNoNetworkDependencies(t *testing.T) {
forbiddenPackages := []string{
"net/http",
"net/rpc",
"golang.org/x/net",
// Analytics/telemetry packages
"github.com/getsentry",
"github.com/DataDog",
}
// Verifies no network-dependent imports exist
}
func TestNoTelemetry(t *testing.T) {
telemetryPackages := []string{
"sentry", "datadog", "newrelic", "bugsnag",
"segment", "mixpanel", "amplitude",
}
// Verifies no telemetry package imports exist
}
Secure Build Practices#
Build Configuration#
The project uses secure build flags in justfile for hardened binaries:
# Secure release build
build-release:
@CGO_ENABLED=0 {{ mise_exec }} go build -trimpath -ldflags="-s -w" -o {{ binary_name }} main.go
Security flags explained:
CGO_ENABLED=0- Static compilation without C dependencies (prevents dynamic library attacks)-trimpath- Removes local file system paths for reproducible builds-ldflags="-s -w"- Strips debug information (-s) and symbol tables (-w)
GoReleaser configuration in .goreleaser.yaml applies these flags to all release builds:
builds:
- id: opndossier
binary: opndossier
main: ./main.go
env:
- CGO_ENABLED=0
goos: [freebsd, linux, darwin, windows]
goarch: [amd64, arm64]
flags:
- -trimpath
ldflags:
- >-
-s -w
-X main.version={{.Version}}
-X main.commit={{.Commit}}
-X main.date={{ .CommitDate }}
mod_timestamp: "{{ .CommitTimestamp }}"
Security Scanning#
Local scanning commands:
# Run gosec security scanner
just scan
# Generate SBOM (Software Bill of Materials)
just sbom
# Run all security checks
just security-all
scan:
@echo "Running security scan..."
@{{ mise_exec }} gosec ./...
Note: gosec runs locally but is not integrated into CI/CD pipeline. Continuous vulnerability scanning is handled by .github/workflows/security.yml which runs govulncheck, CodeQL, and Trivy on push/PR and weekly.
SBOM Generation#
Multiple SBOM generation approaches:
sboms:
- id: binary-sbom
documents:
- "{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.bom.json"
artifacts: binary
cmd: cyclonedx-gomod
args: ["bin", "-json", "-output", "$document", "$artifact"]
- id: module-sbom
documents:
- "{{ .ProjectName }}_{{ .Version }}_module.bom.json"
artifacts: any
cmd: cyclonedx-gomod
args: ["mod", "-licenses", "-std", "-json", "-output", "$document", "../"]
SBOM generation in CI is gated to push-to-main only:
sbom:
name: Generate SBOM
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Build binary
run: just build-release
- name: Generate SBOM from binary (CycloneDX)
uses: CycloneDX/gh-gomod-generate-sbom@efc74245d6802c8cefd925620515442756c70d8f
with:
args: bin -output sbom-binary.cyclonedx.json ./opndossier
The authoritative SBOMs are generated by GoReleaser at release time (see .goreleaser.yaml sboms: section). .github/workflows/sbom.yml runs on a weekly schedule to catch dependency drift between releases.
Supply Chain Security#
SLSA Provenance#
SLSA Level 3 provenance generation in release workflow:
provenance:
needs: goreleaser
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- name: Generate attestation for checksums
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: dist/*checksums*.txt
- name: Generate attestation for archives
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: |
dist/*.tar.gz
dist/*.zip
Cosign Keyless Signing#
Artifact signing with Cosign and Sigstore:
signs:
- id: checksum-cosign
cmd: cosign
signature: "${artifact}.sigstore.json"
args:
- "sign-blob"
- "--bundle=${signature}"
- "${artifact}"
- "--yes"
artifacts: checksum
output: true
Signature verification in release workflow:
- name: Verify checksum signature
run: |
cosign verify-blob \
--certificate-identity "https://github.com/${GITHUB_REPOSITORY}/.github/workflows/release.yml@${GITHUB_REF}" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--bundle "$SIGNATURE" \
"$CHECKSUMS"
Additional Security Monitoring#
- OSSF Scorecard - Security health metrics
- Dependabot - Automated dependency updates
- govulncheck - Go vulnerability database scanning (continuous via security.yml)
- CodeQL - Semantic static application security testing (continuous via security.yml)
- Trivy - Filesystem scan + misconfiguration detection (continuous via security.yml)
Implementation and Enforcement#
Code Quality Standards#
The project enforces comprehensive quality requirements:
- Linting - All code must pass
golangci-lint - Test Coverage - >80% coverage required for new functionality
- Documentation - User-facing changes require documentation updates
- Go Best Practices - Follow Google Go Style Guide
- Pre-commit Checks - All checks must pass before PR submission
Error Handling Pattern#
Consistent error wrapping throughout the codebase:
// Validation errors with wrapped context
func validateLevel(level string) error {
switch strings.ToLower(level) {
case "debug", "info", "warn", "warning", "error", "":
return nil
default:
return fmt.Errorf("%w: %s", ErrInvalidLogLevel, level)
}
}
// Parser errors with location context
func handleXMLError(err error, dec *xml.Decoder) error {
if wrappedErr := WrapXMLSyntaxErrorWithOffset(err, "opnsense", dec); wrappedErr != nil {
return fmt.Errorf("failed to decode XML: %w", wrappedErr)
}
return fmt.Errorf("failed to read token: %w", err)
}
// Plugin manager errors with operation context
result, err := pm.registry.RunComplianceChecks(device, pluginNames)
if err != nil {
return nil, fmt.Errorf("compliance audit failed: %w", err)
}
Custom error types with Unwrap support in internal/export/file.go:
type Error struct {
Operation string
Path string
Message string
Cause error // Wrapped error
}
func (e *Error) Error() string {
if e.Cause != nil {
return fmt.Sprintf("export %s failed for %s: %s (caused by: %v)",
e.Operation, e.Path, e.Message, e.Cause)
}
return fmt.Sprintf("export %s failed for %s: %s",
e.Operation, e.Path, e.Message)
}
func (e *Error) Unwrap() error {
return e.Cause
}
Structured Logging#
charmbracelet/log wrapper in internal/logging/logger.go:
type Logger struct {
*log.Logger
}
type Config struct {
Level string // "debug", "info", "warn", "error"
Format string // "text" or "json"
Output io.Writer
ReportCaller bool
ReportTimestamp bool
}
// Usage examples
logger.Info("Starting conversion", "input_file", inputPath)
logger.Debug("Processing section", "section", sectionName, "count", itemCount)
// Field-based logging
fieldLogger := logger.WithFields("key1", "value1", "key2", "value2")
fieldLogger.Info("test message")
// Sub-logger with subsystem
subLogger := logger.Sub("parser")
subLogger.Info("test message") // Adds "subsystem": "parser"
Logger configuration from cmd/root.go:
defaultLoggerConfig = logging.Config{
Level: "info",
Format: "text",
Output: os.Stderr,
ReportCaller: true,
ReportTimestamp: true,
}
Testing Standards#
Table-driven tests in internal/cfgparser/xml_test.go:
func TestXMLParser_Parse(t *testing.T) {
tests := []struct {
name string
input string
expected *schema.OpnSenseDocument
wantErr bool
}{
{
name: "valid config",
input: `<opnsense><version>1.2.3</version></opnsense>`,
expected: &schema.OpnSenseDocument{Version: "1.2.3"},
wantErr: false,
},
{
name: "invalid xml",
input: `<opnsense><version>1.2.3</version>`,
expected: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := NewXMLParser()
result, err := p.Parse(context.Background(), strings.NewReader(tt.input))
if tt.wantErr {
require.Error(t, err)
assert.Nil(t, result)
} else {
require.NoError(t, err)
assert.Equal(t, tt.expected.Version, result.Version)
}
})
}
}
Benchmark tests in internal/cfgparser/xml_test.go:
func BenchmarkXMLParser_Parse(b *testing.B) {
sampleFile := filepath.Join("testdata", "config.xml.sample")
parser := NewXMLParser()
for b.Loop() {
file, err := os.Open(sampleFile)
if err != nil {
b.Fatal(err)
}
_, err = parser.Parse(context.Background(), file)
if err != nil {
_ = file.Close()
b.Fatal(err)
}
_ = file.Close()
}
}
Test commands from justfile:
just test # Run all tests
just test-coverage # Generate coverage report
just test-race # Run with race detector
just coverage # Open coverage in browser
just bench # Run benchmarks
Pre-commit Hooks#
Pre-commit configuration in .pre-commit-config.yaml:
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-added-large-files
args: ["--maxkb=1024"]
- id: check-case-conflict
- id: check-merge-conflict
- id: check-yaml
- id: check-json
- id: end-of-file-fixer
- id: trailing-whitespace
install:
@mise install
@{{ mise_exec }} pre-commit install --hook-type pre-commit --hook-type commit-msg --hook-type pre-push
@{{ mise_exec }} go mod tidy
CI/CD Pipeline#
Comprehensive CI checks in .github/workflows/ci.yml:
- Lint job - golangci-lint with strict configuration
- Test jobs - Multi-platform (Ubuntu, macOS, Windows) and Go version matrix (stable, oldstable) with >80% coverage
- Integration tests - Real OPNsense configuration parsing
- Coverage reporting - Codecov integration
- Build verification - Ensure clean builds on all platforms
- SBOM generation - CycloneDX (push-to-main only)
Local CI equivalent:
just ci-check # Runs all CI checks locally
Pull Request Requirements#
PR process from CONTRIBUTING.md:
- All automated CI checks must pass
- Commit messages must follow Conventional Commits
- All commits require DCO sign-off (
git commit -s) - Minimum 80% test coverage for new code
- At least one maintainer review required
- Breaking changes require community discussion
Relevant Code Files#
| File Path | Purpose | Key Security/Standards Features |
|---|---|---|
justfile | Build automation | Secure build flags, gosec integration, SBOM generation |
.goreleaser.yaml | Release configuration | Secure builds, SBOM, Cosign signing |
cliff.toml | Changelog generation | Conventional Commits parsing, filtering |
.pre-commit-config.yaml | Pre-commit hooks | File validation, format checking |
internal/cfgparser/xml.go | XML parsing | Size limits, XXE prevention, charset validation |
internal/cfgparser/errors.go | Error handling | Structured errors without sensitive data exposure |
internal/config/config.go | Configuration | OPNDOSSIER_* env var handling, secure defaults |
internal/sanitizer/patterns.go | Pattern detection | IP, password, certificate detection |
internal/sanitizer/sanitizer.go | Data sanitization | Sensitive data redaction |
internal/validator/opnsense.go | Configuration validation | IP, port, semantic validation |
internal/security_test.go | Security testing | No network, no telemetry verification |
internal/logging/logger.go | Structured logging | charmbracelet/log wrapper |
.github/workflows/ci.yml | CI pipeline | Multi-platform testing, Go version matrix, SBOM generation |
.github/workflows/security.yml | Security scanning | govulncheck, CodeQL, Trivy (continuous + weekly) |
.github/workflows/release.yml | Release workflow | SLSA provenance, Cosign verification |
.github/workflows/scorecard.yml | Security scorecard | OSSF security metrics |
Related Topics#
- Contributing Guidelines - Complete contributor workflow
- Development Standards - Detailed coding standards
- Release Process - Release workflow and supply chain security
- Testing Strategies - Unit, integration, and benchmark testing approaches
- CI/CD Architecture - GitHub Actions workflow design
- Supply Chain Security - SLSA, Sigstore, and provenance
- Developer Certificate of Origin - DCO sign-off requirement
- Conventional Commits - Commit message specification