ADR-014: Extract BinaryEvtxWriter to the go-evtx library¶
Status: accepted Date: 2026-08-08
Context¶
ADR-009 chose to implement BinaryEvtxWriter
from scratch inside cee-exporter, on the grounds that no pure-Go EVTX writer
library existed. That implementation grew, across Phases 7 through 9, into a
substantial body of format-specific code: BinXML template encoding, chunk
assembly and CRC32 checksums, file rotation, and round-trip verification
against an external parser. None of that code has any dependency on CEPA,
WindowsEvent, or anything else specific to this exporter — its correctness
concerns (does the chunk header CRC match, does python-evtx parse the output,
does a multi-chunk file rotate correctly) are entirely separate from cee-exporter's
own concerns (does a CEPA batch parse, does the queue drop events under
backpressure).
Two later ADRs already assume a separate library exists without either of them being the decision that created it:
- ADR-012 places the periodic flush
ticker "in the go-evtx library layer, inside the
Writerstruct" and reasons explicitly aboutgithub.com/fjacquet/go-evtxas a standalone, independently-versioned dependency. - ADR-013 describes
go-evtx v0.1.0's write-on-close model and plans its Phase 10 supersession entirely in terms of that library's internals.
This ADR records the decision both of them presuppose.
Decision¶
Extract the EVTX binary-format encoder out of cee-exporter into a standalone
library, github.com/fjacquet/go-evtx, and consume it as a direct
dependency. pkg/evtx/writer_evtx_notwindows.go is reduced to an adapter:
it translates a WindowsEvent into the map[string]string shape go-evtx's
WriteRecord expects, and delegates all binary encoding, chunk management,
rotation, and flush-ticker lifecycle to the library.
The extraction landed in commit 29ed067
("feat(08.5-02): replace BinaryEvtxWriter with go-evtx adapter"), which cut
writer_evtx_notwindows.go from 542 lines of format logic (the file at
29ed067's parent, b4c0155) down to a 68-line adapter, and removed
evtx_binformat.go and its test file entirely — that code, and its tests,
now live in go-evtx. The dependency has since been bumped through v0.2.0
(Phase 9's RotationConfig/flush-ticker wiring), v0.5.1, to the v0.6.0
currently in go.mod (Task 12, same day as this ADR).
Consequences¶
Positive:
- EVTX format correctness — BinXML templates, chunk CRCs, rotation, external parser compatibility — is now testable and versioned independently of cee-exporter, and reusable by other consumers (e.g. forensics tooling) that have no interest in CEPA at all.
- cee-exporter's own
pkg/evtxpackage shrank dramatically: the adapter translates field names and enforces size limits, nothing more. - A format bug can be fixed and released in go-evtx without a cee-exporter
code change beyond a
go.modversion bump.
Negative:
- The "no external dependencies" framing under which ADR-009 was accepted —
and which the README and PRD both repeated — no longer holds for the EVTX
output path.
github.com/fjacquet/go-evtxis a direct, non-stdlib dependency. - A format defect now requires a coordinated release across two repositories
instead of one. The 2026-08-08 audit
(
docs/superpowers/specs/2026-08-08-promise-remediation-design.md) made this concrete: it found that go-evtxv0.5.1silently truncates oversized records instead of rejecting them, producing a.evtxfile whose chunk CRCs verify but which no parser can read — a defect this repo cannot fix directly. The durability and format-correctness fixes are scoped to ship as go-evtxv0.6.0, which had not landed at the time of this writing; cee-exporter cannot benefit from that fix until it bumps its dependency. In the meantime, this repo added its own defensive caps inwindowsEventToFields(capping any filesystem-controlled field before it reaches go-evtx, and guardingBinaryEvtxWriter.Closeagainst go-evtx's non-idempotentClose) precisely because the upstream defect could not be fixed on cee-exporter's own timeline.writer_evtx_notwindows.gois 228 lines today — far more than the 68 lines the extraction itself produced. None of that growth is a reversion to encoding EVTX format internals in this repo: 8 lines were unrelated feature work two commits later (be78c36, addingRotate()and SIGHUP support), and the rest is this release's defensive hardening (65f36c5,1857973) — capping oversized fields and guarding against doubleClose.
Update (2026-08-08, Task 12): go-evtx
v0.6.0shipped the same day as the paragraph above was written, and this repo bumped to it the same day.WriteRecordnow returnsErrRecordTooLargeinstead of silently truncating an oversized record,Close()is now idempotent, a failedrotate()now sets a permanent error on the writer instead of silently discarding every subsequent event, and archive filenames now carry nanosecond resolution so two rotations in the same second no longer overwrite each other. The two-repository coordination cost described above is not being retracted — it is what just happened: the fix landed upstream first, and this repo could not benefit from it until this version bump. The defensive caps and theCloseguard inwriter_evtx_notwindows.gowere deliberately kept rather than removed (see that file's own comments) as defense-in-depth against a class of defect this dependency has already shipped once, not because the v0.6.0 fix is believed incomplete; the 228-line figure above does not shrink as a result of this bump.
Supersedes¶
ADR-009. Its "No new production
dependencies added to go.mod" claim (ADR-009, Consequences) is void: as of
this decision, github.com/fjacquet/go-evtx is a direct production
dependency.
References¶
- Commit
29ed067— "feat(08.5-02): replace BinaryEvtxWriter with go-evtx adapter" docs/superpowers/specs/2026-08-08-promise-remediation-design.md— the 2026-08-08 audit that surfaced the unrecorded reversal and the go-evtxv0.5.1truncation defect- go-evtx v0.6.0 release notes
and
CHANGELOG.md— the durability release fixing the truncation defect above plus the non-idempotentClose, the failed-rotation silent-discard path, and the sub-second archive-name collision - Companion spec in the go-evtx repository:
go-evtx/docs/superpowers/specs/2026-08-08-durability-and-format-correctness-design.md - ADR-012, ADR-013 — decisions made against go-evtx's internals without this ADR existing yet