Detach callback values before publishing configuration

This commit is contained in:
2026-09-06 19:05:28 -05:00
parent 9c94f2b70a
commit f7ca150a7f
5 changed files with 91 additions and 7 deletions

16
load.go
View File

@@ -252,6 +252,14 @@ func loadWithSecretLookup[T any](source Source, lookup secretLookup, supplied ..
accumulator.add(Diagnostic{Code: CodeExplainLimitExceeded, Severity: SeverityError, Message: "retained configuration history exceeds the node limit"})
return nil, accumulator.loadError()
}
// Resolver assignments may borrow application-owned mutable values. Detach
// before secret resolution mutates reference fields or validation sees them.
detached, ownershipIssue := cloneConfiguration(schema, reflect.ValueOf(configuration), defaultProjectionLimits())
if ownershipIssue != nil {
accumulator.add(diagnosticFromDecodeIssue(decodeIssueFromProjection(ownershipIssue)))
return nil, accumulator.loadError()
}
configuration = detached.Interface().(T)
secretResolution := resolveSecretReferences(reflect.ValueOf(&configuration).Elem(), schema, inputDocument, lookup)
accumulator.add(secretResolution.diagnostics...)
if hasErrors(secretResolution.diagnostics) {
@@ -264,6 +272,14 @@ func loadWithSecretLookup[T any](source Source, lookup secretLookup, supplied ..
return nil, accumulator.loadError()
}
}
// A read-only validator can replace a value with an equal borrowed object.
// Projected mutation checks cannot detect that alias, so detach at publication.
published, ownershipIssue := cloneConfigurationState(schema, reflect.ValueOf(configuration), defaultProjectionLimits(), true)
if ownershipIssue != nil {
accumulator.add(diagnosticFromDecodeIssue(decodeIssueFromProjection(ownershipIssue)))
return nil, accumulator.loadError()
}
configuration = published.Interface().(T)
secrets := classifySecretPaths(schema, options.Definition.Secret, metadata, final, applications, decoded.baseline, decoded.final)
return &Result[T]{
Config: configuration,