# Security Audit Reviewed revision: `b30c9f167dba16a9192d9bfde4aeb2b9c597ce0e`. Audit date: 2026-10-25. ## Scope And Trust Model This review examined input validation and budgets, import/discovery containment, link resolution, collection authority, application callbacks, secret references and bootstrap, historical sensitivity, diagnostics, effective rendering and diffs, and the runtime/development dependency boundary. Knowledge-graph searches and caller traces were supplemented by source inspection, existing tests, a bounded standalone diagnostic, and an advisory-database spot-check. The [architecture](../policy/architecture.md) and [configuration contract](../config.md) establish these boundaries: - ConfigKit is a synchronous startup library, not a network service or an authentication/authorization framework. Applications select inputs, roots, filesystem implementations, callbacks, and credential use. Applications own remote acquisition and authentication of that data. - The public facade delegates to a private engine and private document/YAML packages. Input composition and inspection do not execute shell commands or launch a background service. Ordinary loading does not change the process environment; explicit secrets-directory bootstrap is the narrow exception. - Host imports use canonical containment checks and owned rooted access under the documented stable-input assumption. Borrowed native filesystems retain their own behavior and are explicitly not a general sandbox guarantee. - Secret references resolve after composition and application resolution. The trusted returned configuration can expose credentials through `Value()`; inspection retains names rather than credentials. Tags, a path predicate, inheritance, and recorded dependencies govern redaction of other values. Application-supplied messages and unrecorded secret transformations remain application responsibilities. One new availability finding warrants remediation. No new credential disclosure, command injection, application-authority bypass, or host containment escape was established. This is a source-level audit with bounded checks, not a certification that every input, dependency, platform, or deployment is secure. ## 1. Finite Symlink Reuse Can Cause Exponential Import-Resolution Work Status: open. Priority: medium. Category: availability / input amplification. **Relevant code.** `resolveHostImportPath` and `resolveNativeImportPath` in [filesystem_links.go](../../internal/engine/filesystem_links.go), lines 129–242, expand link-target components with a frame stack and track only currently active links. Completion removes a link from that set; a later use resolves its entire target again. No completed-target result is reused. `importEntryTarget` invokes these resolvers for discovered links. Both [directory selection](../../internal/engine/import_selection.go) and [glob selection](../../internal/engine/glob.go) reach it. Directory selection resolves the target before deciding whether recursive descent is enabled, so `recursive: false` does not avoid this work. Native resolution is used when the borrowed filesystem provides `fs.ReadLinkFS`; host resolution uses the analogous rooted implementation. **Plausible scenario.** An application loads a configuration tree whose imported assets can be supplied by a less-trusted user or configuration provider. That provider controls link entries inside an allowed import directory. The application's own main file need only select that directory with link following enabled, which is the current default. No concurrent filesystem mutation, outside-root access, executable callback, or malicious filesystem implementation is required. This is conditional on influence over imported filesystem data; it is not an anonymous remote vulnerability in ConfigKit by itself. Inside an ordinary `links` directory, create this finite graph: ```text s00 -> . s01 -> s00/s00 s02 -> s01/s01 s03 -> s02/s02 ... ``` Each link resolves to the same contained directory. It has no active link cycle: the first reference to a lower link finishes before the second begins. Resolving level N nevertheless repeats the level N-1 expansion twice, giving a recurrence of `T(N) = 2T(N-1) + O(1)` component work for this fixed-name fixture. Directory enumeration contains only N+1 entries, and no imported configuration nodes or new import edges account for those repeated expansions. Consequently, source, entry, input-node, and active-import-depth budgets do not bound this work by the size of the selected graph. This is repeated resolution of a compact graph, not genuinely expanding configuration output or ordinary deterministic sorting. **Evidence.** A standalone public-API diagnostic created real temporary symlinks and exposed them through `NewFilesystem` backed by `os.DirFS`. A thin `fs.ReadLinkFS` wrapper counted metadata/link operations and imposed a diagnostic ceiling of 12,000 `Lstat` calls. The main input selected `dir: links` with link following enabled and default nonrecursive discovery. Successful loads produced these counts: | Link entries | `Lstat` calls | `ReadLink` calls | | --- | ---: | ---: | | 3 | 32 | 11 | | 5 | 128 | 57 | | 7 | 512 | 247 | | 9 | 2,048 | 1,013 | | 11 | 8,192 | 4,083 | A 13-entry fixture reached the diagnostic ceiling and returned a safe load failure when the wrapper stopped it. The same fixture with `follow: false` completed with 17 `Lstat` calls and zero `ReadLink` calls. The operation counts substantiate amplification in the native resolver; the host resolver's matching frame-expansion logic was verified by source inspection and caller tracing, not a separate host operation-count test. No timings, large attack graph, or external-system exploitation were used. **Impact.** A small imported asset tree can monopolize startup/load execution for an impractically long period. Applications that expose configuration validation or repeated loading to less-trusted providers can also tie up their own workers. The normal trusted-operator-only deployment reduces the adversarial exposure, but accidental alias graphs can cause the same availability failure. **Potential remediation.** Reuse fully resolved link-target results within the resolution operation, keyed by the appropriate rooted namespace and full link path, while retaining active-path cycle detection and containment checks. Equivalent dependency-based evaluation is another option. Preserve legitimate finite reuse, relative/absolute host-link semantics, and the stable-input model; do not replace the active set with a global "already visited means cycle" rule. Review both resolvers and directory/glob entry points. The objective is to avoid re-expanding completed dependencies, not to introduce a public cache or reject valid graphs with an arbitrary link-count cap. Consumers can already opt out of following discovered links where appropriate; changing that default is not part of this recommendation. ## Previously Reported Risks The [correctness audit](correctness.md) already records pointer-only recursive schema nontermination, failed dynamic-descriptor cache state, and callback guards left active during panic unwinding. The [resource audit](resources.md) owns pre-budget typed-container allocation, incomplete retained-history accounting, and an import-root cleanup gap after callback panic. These can affect availability when their documented preconditions hold; they are not newly discovered remote exploits or duplicate remediation tasks here. The [error-handling audit](error-handling.md) separately owns silently omitted directory contributions after certain enumeration/close failures. The [efficiency audit](efficiency.md) owns previously identified avoidable batch costs. This review does not reclassify every expensive operation or application programming error as a security vulnerability. ## Dependency Spot-Check The ordinary package's non-standard-library dependency graph contains only `go.yaml.in/yaml/v4 v4.0.0-rc.6`; ReleaseKit, Goldmark, and `x/mod` are maintainer tool dependencies rather than ConfigKit runtime imports. Cached module integrity verification passed. The current [Go vulnerability database module index](https://vuln.go.dev/index/modules.json) contained no entries for the selected YAML v4, ReleaseKit, or Goldmark v2 module paths. Its two `golang.org/x/mod` reports, [GO-2026-6179](https://pkg.go.dev/vuln/GO-2026-6179) and [GO-2026-6180](https://pkg.go.dev/vuln/GO-2026-6180), identify v0.40.0 as fixed, which is the version selected here. The related toolchain fixes also precede the Go 1.26.10 toolchain used for this audit. The database metadata reported a modified time of `2026-10-21T00:27:29Z` when checked. This was a module-path/advisory spot-check, not a complete standard-library, toolchain, transitive-reachability, or supply-chain attestation. `govulncheck`, `gosec`, and `gitleaks` were not installed; no scanners were added to the project. Absence of an advisory is not proof of absence of vulnerabilities, and `go mod verify` does not audit dependency behavior or establish that upstream content is safe. The [database API documentation](https://go.dev/doc/security/vuln/database) describes the advisory source used for this check. ## Validation And Preserved Boundaries The following passed with Go 1.26.10 on Linux amd64 using cached dependencies and disabled automatic dependency/toolchain acquisition: ```sh go test -race ./... -count=1 go test ./internal/engine -run 'Secret|Sensitive|Sensitivity|Contain|Escap|Outside|Symlink|Nonregular|Budget|Limit|Diagnostic' -count=1 go vet ./... go mod verify ``` These checks include existing canary-based non-disclosure, historical and bilateral diff sensitivity, safe diagnostic, parser-policy, containment, and nonregular-file cases. The bounded link diagnostic ran outside the repository. No production code or repository tests were added or changed; no native macOS/Windows run, active external exploitation, or exhaustive fuzzing was done. This report does not propose authentication in the library, arbitrary environment name restrictions, secret-file permission requirements, retries, memory erasure, or protection against privileged concurrent filesystem mutation. Those would need their own concrete requirement and threat analysis. It preserves borrowed filesystem ownership, application-owned safe callback messages, public secret reference names, explicit partial bootstrap semantics, and the current release gates.