Propagate source evidence sensitivity through history

This commit is contained in:
2026-10-15 16:00:01 +00:00
parent fed591183b
commit 1e47ee21cd
4 changed files with 128 additions and 44 deletions

View File

@@ -92,6 +92,40 @@ func TestInheritanceSensitivityIncludesSchemaTaggedSource(t *testing.T) {
}
}
func TestSourceVersionEvidenceSensitivityFollowsInheritanceAndDisplacedHistory(t *testing.T) {
const canary = "EVIDENCE-INHERITANCE-CANARY"
input := parseCompositionDocument(t, "source-1", "parent: {branch: {token: "+canary+", plain: visible}}\nchild: {extends: parent, branch: public}\n")
inspection, _ := inheritanceInspectionForTest(t, input)
secretPath := Keys("parent", "branch", "token")
evidence := &sourceVersionEvidence{
path: secretPath.clone(), resolvedPath: secretPath.clone(),
version: Version{Present: true, Text: canary},
ancestors: []sourceVersionAncestor{{}, {path: Keys("parent")}, {path: Keys("parent", "branch")}, {path: secretPath.clone()}},
}
inspection.secrets = classifySecretPathsWithEvidence(nil, func(path Path) bool {
return path.String() == secretPath.String()
}, inspection.metadata, inspection.final, inspection.applications, []*sourceVersionEvidence{evidence}, inspection.input)
path := Keys("child", "branch", "token")
explanation, found := inspection.Explain(path)
if !found || explanation.Effective.Present {
t.Fatalf("displaced inherited path = %#v, %v", explanation, found)
}
for _, change := range explanation.History {
if strings.Contains(change.Before.YAML, canary) || strings.Contains(change.After.YAML, canary) ||
(change.After.Present && !change.After.Redacted) {
t.Fatalf("displaced inherited canary escaped redaction: %#v", explanation.History)
}
}
plain, found := inspection.Explain(Keys("parent", "branch", "plain"))
if !found || plain.Effective.Redacted || plain.Effective.YAML != "visible\n" {
t.Fatalf("neighboring value was classified with evidence: %#v, %v", plain, found)
}
if _, found := inspection.Metadata().Lookup(Keys("evidence-only")); found {
t.Fatal("evidence classification created a metadata path")
}
}
func TestInheritedSecretReferencesResolveOnlyAfterExpansion(t *testing.T) {
type entry struct {
Credential Secret `yaml:"credential"`

View File

@@ -75,19 +75,6 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
classifyProjection(tree)
}
classifyProjection(final)
evidenceSecrets := make(map[string]bool)
seenEvidence := make(map[string][]Path)
classifyEvidence := func(destination, local Path) {
path := appendDestination(destination, local)
pointer := path.String()
for _, seen := range seenEvidence[pointer] {
if sameSecretPath(seen, path) {
return
}
}
seenEvidence[pointer] = append(seenEvidence[pointer], path.clone())
classifyEvidencePath(evidenceSecrets, schema, predicate, path)
}
for _, item := range evidence {
if item == nil {
continue
@@ -96,9 +83,9 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
if !candidate.valid() {
candidate = item.path
}
classifyEvidence(item.destination, candidate)
addSecretEvidencePath(paths, item.destination, candidate)
for _, ancestor := range item.ancestors {
classifyEvidence(item.destination, ancestor.path)
addSecretEvidencePath(paths, item.destination, ancestor.path)
}
}
for pointer, path := range paths {
@@ -109,36 +96,12 @@ func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Pa
secret[pointer] = true
}
}
classified := closeSecretDependencies(paths, secret, dependencies, inherited)
for pointer, sensitive := range evidenceSecrets {
if sensitive {
classified[pointer] = true
}
}
return classified
return closeSecretDependencies(paths, secret, dependencies, inherited)
}
func classifyEvidencePath(secret map[string]bool, schema *schemaDescriptor, predicate func(Path) bool, path Path) {
pointer := path.String()
if root, ok := schemaSecretRoot(schema, path); ok {
secret[root.String()] = true
}
if predicate != nil && predicate(path.clone()) {
secret[pointer] = true
}
}
func sameSecretPath(left, right Path) bool {
if len(left.segments) != len(right.segments) {
return false
}
for index, segment := range left.segments {
other := right.segments[index]
if segment.kind != other.kind || segment.key != other.key || segment.index != other.index {
return false
}
}
return true
func addSecretEvidencePath(paths map[string]Path, destination, local Path) {
path := appendDestination(destination, local)
paths[path.String()] = path.clone()
}
func addSecretPath(paths map[string]Path, pointer string) {

View File

@@ -974,6 +974,74 @@ func TestSourceVersionUnderlaysProtectRemainingMountedPayload(t *testing.T) {
}
}
func TestSourceVersionEvidencePropagatesThroughRecordedResolutionDependency(t *testing.T) {
t.Parallel()
type credentials struct {
Password string `yaml:"password"`
Public string `yaml:"public"`
}
type configuration struct {
Credentials credentials `yaml:"credentials"`
Derived string `yaml:"derived"`
}
load := func(header string) *Loaded[configuration] {
t.Helper()
loaded, err := LoadLayered(Input{
Source: Bytes("main", []byte("credentials: {password: main-value}\n")),
}, testSecretBinding(), LayeredOptions[configuration]{
SourceVersionMode: sourceVersionModeIfPresent,
Underlays: []Underlay{{Inputs: []Input{{Source: Bytes("underlay", []byte(
"credentials:\n version: "+header+"\n public: visible-neighbor\n",
))}}}},
Definition: Definition[configuration]{
Version: &VersionRule{
Path: Keys("credentials", "version"),
Check: func(Version, *Validation) error { return nil },
},
Secret: func(path Path) bool {
return path.String() == "/credentials/version"
},
Resolve: func(value *configuration, resolution *Resolution) error {
value.Derived = value.Credentials.Password
resolution.Record(Keys("derived"), ChangeNormalization, "derive from credentials", Keys("credentials"))
return nil
},
},
})
if err != nil || loaded == nil {
t.Fatalf("LoadLayered with header %q = %#v, %v", header, loaded, err)
}
return loaded
}
left, right := load("202"), load("303")
if left.Config.Derived != "main-value" || right.Config.Derived != "main-value" {
t.Fatalf("derived values = %q, %q", left.Config.Derived, right.Config.Derived)
}
if !left.Inspection.secrets["/derived"] || !right.Inspection.secrets["/derived"] {
t.Fatalf("evidence did not activate recorded dependency closure: left=%#v right=%#v", left.Inspection.secrets, right.Inspection.secrets)
}
derived, found := left.Inspection.Explain(Keys("derived"))
if !found || !derived.Effective.Redacted {
t.Fatalf("derived explanation was not redacted: %#v, found=%v", derived, found)
}
public, found := left.Inspection.Explain(Keys("credentials", "public"))
if !found || public.Effective.Redacted || public.Effective.YAML != "visible-neighbor\n" {
t.Fatalf("neighboring payload = %#v, found=%v", public, found)
}
headerPath := Keys("credentials", "version")
if _, found := left.Inspection.Explain(headerPath); found {
t.Fatal("consumed declaration created an effective or historical explanation path")
}
if _, found := left.Inspection.Metadata().Lookup(headerPath); found {
t.Fatal("consumed declaration created a metadata path")
}
if differences, err := Diff(left.Inspection, right.Inspection); err != nil || len(differences) != 0 {
t.Fatalf("different consumed headers changed effective diff: %#v, %v", differences, err)
}
}
func TestSourceVersionSelectedOverlayConsumesHeaderAndRetainsWarning(t *testing.T) {
t.Parallel()