Summary
The provider read path (GetData) returns references to internal storage, so a caller or engine that mutates the returned map corrupts the provider's data and can race with concurrent readers. This undermines the concurrency guarantee the write path (AddDataToContext) maintains.
1. ContextProvider.GetData returns the live stored map
platform/data/contextProvider.go:44:
d, ok := value.(map[string]any)
...
return d, nil // the exact map stored in the context, no copy
AddDataToContext deep-copies at every level to guarantee derived-context independence (see the # Concurrency note at contextProvider.go:53-61), but GetData hands out the shared storage. A caller mutation corrupts the parent context, all sibling derived chains, and all future GetData results, and races under -race with a concurrent AddDataToContext copy or another GetData.
Repro (confirmed): goroutine A runs d, _ := p.GetData(ctx); d["k"] = "hacked" while goroutine B evaluates with the same ctx → B observes A's mutation; under -race the pair is a data race.
Fix: return deepCopyMap(d), nil.
2. StaticProvider.GetData shallow clone contradicts its doc
platform/data/staticProvider.go:32-35:
// GetData returns the static data map, cloned to prevent modification.
func (p *StaticProvider) GetData(_ context.Context) (map[string]any, error) {
return maps.Clone(p.data), nil // shallow; nested maps/slices shared
}
maps.Clone is shallow, so nested maps and slices remain shared with p.data, contradicting the "cloned to prevent modification" doc.
Repro (confirmed): d, _ := p.GetData(ctx); d["cfg"].(map[string]any)["x"] = 999 permanently corrupts the provider's static data; every later GetData/eval sees 999. Concurrent mutation plus GetData races on the shared inner map. CompositeProvider.GetData amplifies this: deepMerge stores dst-only nested maps by reference (compositeProvider.go:58), so the merged result aliases the static provider's internals.
Fix: use the existing deepCopyMap (contextProvider.go:187) instead of maps.Clone, and align the doc with the behavior.
Impact
Medium; high if any engine mutates its input map in place. The library advertises thread-safe data management, which the read path breaks for any consumer that touches the returned map.
Fix
Deep-copy on the read path in both providers (reuse deepCopyMap), and add -race tests that mutate the returned map and assert the provider's state is unchanged.
Summary
The provider read path (
GetData) returns references to internal storage, so a caller or engine that mutates the returned map corrupts the provider's data and can race with concurrent readers. This undermines the concurrency guarantee the write path (AddDataToContext) maintains.1.
ContextProvider.GetDatareturns the live stored mapplatform/data/contextProvider.go:44:AddDataToContextdeep-copies at every level to guarantee derived-context independence (see the# Concurrencynote atcontextProvider.go:53-61), butGetDatahands out the shared storage. A caller mutation corrupts the parent context, all sibling derived chains, and all futureGetDataresults, and races under-racewith a concurrentAddDataToContextcopy or anotherGetData.Repro (confirmed): goroutine A runs
d, _ := p.GetData(ctx); d["k"] = "hacked"while goroutine B evaluates with the samectx→ B observes A's mutation; under-racethe pair is a data race.Fix:
return deepCopyMap(d), nil.2.
StaticProvider.GetDatashallow clone contradicts its docplatform/data/staticProvider.go:32-35:maps.Cloneis shallow, so nested maps and slices remain shared withp.data, contradicting the "cloned to prevent modification" doc.Repro (confirmed):
d, _ := p.GetData(ctx); d["cfg"].(map[string]any)["x"] = 999permanently corrupts the provider's static data; every laterGetData/eval sees999. Concurrent mutation plusGetDataraces on the shared inner map.CompositeProvider.GetDataamplifies this:deepMergestores dst-only nested maps by reference (compositeProvider.go:58), so the merged result aliases the static provider's internals.Fix: use the existing
deepCopyMap(contextProvider.go:187) instead ofmaps.Clone, and align the doc with the behavior.Impact
Medium; high if any engine mutates its input map in place. The library advertises thread-safe data management, which the read path breaks for any consumer that touches the returned map.
Fix
Deep-copy on the read path in both providers (reuse
deepCopyMap), and add-racetests that mutate the returned map and assert the provider's state is unchanged.