audit: config library — violations, issues, smells #12
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
residual/config#12
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
audit — config library
summary
The config library is well-structured with a clean API surface and strong test coverage (93 tests). However, there are three categories of concern: (1) a broken wildcard matching system that tests pass by coincidence, (2) systematic silent error swallowing that makes config debugging nearly impossible, and (3) an inconsistency between the simple and rules-based merge paths that causes
conf:"-"fields to be merged when they shouldn't be. ThehomeDir()fallback to"/"is a silent data-corruption risk.violations
[VIOLATION]
core/merge.go:118-157— scope discipline —mergeStructsiterates by raw index, merging ALL fields includingconf:"-"excluded fields.mergeStructsWithRulescorrectly skips them viaf.Skip || f.RawExclude. Users ofUseConfig/Overwriteget different behavior thanMergeWithRulesfor the same struct. — MakemergeStructsuseinspectFieldsto skip excluded fields, or route all merges throughmergeStructsWithRuleswith nil rules.[VIOLATION]
core/merge.go:279-288— more than nothing — Wildcard patterns likedatabase.*can never match becauseinspectFieldsreturns flat leaf names (e.g.,"host"), not dotted paths (e.g.,"database.host"). TheTestMergeWithRulesWildcardtest passes by coincidence: cascade is the default, so the rule not matching produces the same result as it matching. — Either makeinspectFieldsreturn dotted paths for nested fields, or change the wildcard docs to reflect that only flat names are matched.[VIOLATION]
core/app.go:102-110— more than nothing —homeDir()falls back to"/"when bothos.UserHomeDir()and$HOMEfail, causingdefaultUserDir()to return"/.config". Any write operation would attempt to create files at the filesystem root. This is a silent data-corruption risk. — Return an error fromdefaultUserDir(), or panic, or at minimum log a warning.issues
[ISSUE]
core/env.go:84-87— more than nothing —coerceAndSetsilently swallows all parse errors. A user settingAPP__PORT=abcgets no error and no warning — the old value silently persists. This pattern repeats across the library (F8, F13, F16, F17, F24). — Return errors fromcoerceAndSetand propagate them. At minimum, log a warning.[ISSUE]
core/load.go:105-106— more than nothing —InitWithTemplatediscards errors fromSystemRawandUserRawwith_. A malformed system config file is silently ignored, and the cascade proceeds with zero values. Compare withCustom[T]which does return errors. — Surface these errors, or at minimum log them.[ISSUE]
core/load.go:201-229— touch only what is necessary —loadRawis dead code in production. It is unexported and called only from one test (load_test.go:22). The doc comment says "planned entry point for MergeWithRules" — a feature that was completed without it. — RemoveloadRawor fold its unique behavior intoloadFromPathRaw.[ISSUE]
core/tag.go:44,232— touch only what is necessary —fieldMeta.PkgPathis set at line 232 but never read by any code. ThePkgPath()checks inbuildShadowTypeoperate onreflect.Type, notfieldMeta. — Remove the field.[ISSUE]
core/template.go:14— more than nothing — TheplaceholderReregex requires uppercase placeholders ([A-Z0-9_]).${CONF__console__shell}does NOT match. This is not documented in theRenderTemplatedoc comment or README. — Document the uppercase requirement, or make the regex case-insensitive.[ISSUE]
stubs.go:56,62— no ceremony —UseConfigandCascadeEnvare byte-for-byte identical functions. The doc comment attempts a semantic distinction but the implementation is the same. This is API surface bloat. — RemoveCascadeEnvor make it a type alias.smells
[SMELL]
core/tag.go:52-59—inspectFieldsis not cached. It is called 6-8 times per type during a singleInitWithTemplatecall. TheshadowCachecaches shadow types but not field metadata. For deeply nested structs this compounds. — Cache[]fieldMetabyreflect.Type.[SMELL]
core/env.go:172-173—splitEnvSepcallsstrings.TrimRight(s, "\r\n")thenstrings.TrimSpace(s). The first call is redundant sinceTrimSpacealready trims\rand\n. — Remove theTrimRightcall.[SMELL]
core/write.go:177-183—isRoot()usesuser.Current()which does NSS lookups.os.Geteuid() == 0is simpler and faster. — Replace withos.Geteuid() == 0.[SMELL]
core/app.go:88-90—defaultSystemDir()wraps the constant"/etc"in a function. Adds indirection for no value. — Make it aconst.[SMELL]
core/load.go:231-242—ResidualConfigis defined inload.gobut used acrossmerge.go,env.go,write.go, andstubs.go. It would be more natural in its own file. — Move totypes.goor similar.[SMELL]
core/env.go:189—implementsTextUnmarshalerallocates areflect.Typeon every call. Not cached. Minor in config loading but easy to fix. — Cache the result.[SMELL]
core/codec.go:45-48— TOML decoder discardsMetaDatawhich contains undecoded keys. A config file with unrecognized keys silently ignores them. — Consider logging or returning undecoded keys.notes
[NOTE]
core/env.go:119-163—setSliceandcoerceAndSetdo not handleUint/Uint64types. Undocumented limitation.[NOTE]
core/tag.go:108—omitemptyaccepts"1"and"yes"in addition to"true". Undocumented but more permissive is generally fine.[NOTE]
core/merge.go:50-53— The wildcard limitation (leaf-only matching) is documented in code comments but not in README. The README says "section.* matches all fields under section" which is misleading.[NOTE] No race conditions detected.
shadowCacheusessync.Map. All other state is function-local.[NOTE] No resource leaks detected. All file operations use
os.ReadFile/os.WriteFilewhich handle cleanup.[NOTE]
core/tag.go:193-199— Panic on malformed conf tags is unreachable via normal Go struct tags (Go'sreflect.StructTag.Lookuptruncates at the first"). The panic is defense-in-depth but would crash the program if it ever fired.verification
go build ./...go test -count=1 ./...(93 tests)go vet ./...