package engine import ( "slices" "gitea.maximumdirect.net/eric/ConfigKit/internal/document" ) type secretDependency struct { target Path dependency Path } func classifySecretPaths(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata, final *document.Node, applications []historyApplication, projections ...*document.Node) map[string]bool { return classifySecretPathsWithEvidence(schema, predicate, metadata, final, applications, nil, projections...) } func classifySecretPathsWithEvidence(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata, final *document.Node, applications []historyApplication, evidence []*sourceVersionEvidence, projections ...*document.Node) map[string]bool { paths := make(map[string]Path) candidateShapes := make(map[string][]Path) var candidates []Path addCandidate := func(path Path) { pointer := path.String() for _, prior := range candidateShapes[pointer] { if slices.Equal(path.segments, prior.segments) { return } } cloned := path.clone() candidateShapes[pointer] = append(candidateShapes[pointer], cloned) candidates = append(candidates, cloned) } for _, application := range applications { walkHistoryApplication(application, func(_ Path, path Path, _ *document.Node) { if len(application.sequences) != 0 { addCandidate(path) } else if parsed, err := ParsePath(path.String()); err == nil { addCandidate(parsed) } addSecretPath(paths, path.String()) }) } var dependencies []secretDependency for pointer, history := range metadata.history { addSecretPath(paths, pointer) if path, err := ParsePath(pointer); err == nil { addCandidate(path) } for _, change := range history { paths[change.Path.String()] = change.Path.clone() for _, dependency := range change.Dependencies { paths[dependency.String()] = dependency.clone() addCandidate(dependency) dependencies = append(dependencies, secretDependency{ target: change.Path.clone(), dependency: dependency.clone(), }) } } } var inherited []secretDependency for _, operation := range metadata.inheritance { if operation.control != nil { control := operation.control.path paths[control.String()] = control.clone() addCandidate(control) for _, contributor := range operation.controlOrigin.contributors { paths[contributor.source.String()] = contributor.source.clone() addCandidate(contributor.source) inherited = append(inherited, secretDependency{target: control.clone(), dependency: contributor.source.clone()}) } continue } if operation.transfer == nil { continue } for pointer := range operation.parentOrigins { source, err := ParsePath(pointer) if err != nil { continue } relative, ok := relativeHistoryPath(operation.transfer.from, source) if !ok { continue } destination := appendInheritancePath(operation.transfer.to, relative) paths[pointer] = source paths[destination.String()] = destination.clone() addCandidate(source) addCandidate(destination) inherited = append(inherited, secretDependency{target: destination, dependency: source}) } } secret := make(map[string]bool) classifyProjection := func(tree *document.Node) { walkDocument(tree, Path{}, func(path Path, node *document.Node) { pointer := path.String() addSecretPath(paths, pointer) if parsed, err := ParsePath(pointer); err == nil { addCandidate(parsed) } if node.Sensitive { secret[pointer] = true } }) } for _, tree := range projections { classifyProjection(tree) } classifyProjection(final) classifyPath := func(path Path) { if root, ok := schemaSecretRoot(schema, path); ok { secret[root.String()] = true } if predicate != nil && predicate(path.clone()) { secret[path.String()] = true } } for _, path := range candidates { classifyPath(path) } // Classify each original evidence shape before folding results into pointer // sensitivity. Numeric mapping keys and sequence indices can share a pointer. for _, item := range evidence { if item == nil { continue } for _, resolved := range item.sensitivityPaths() { addSecretPath(paths, resolved.String()) addCandidate(resolved) classifyPath(resolved) } } return closeSecretDependencies(paths, secret, dependencies, inherited) } func addSecretEvidencePath(paths map[string]Path, destination, local Path) Path { path := appendDestination(destination, local) addSecretPath(paths, path.String()) return path } func addSecretPath(paths map[string]Path, pointer string) { if existing, found := paths[pointer]; found && existing.valid() { // Accumulator keys already match their paths' canonical spelling. History // can install typed segments, which still need the parsed replacement. parsed := true for _, segment := range existing.segments { if segment.kind != pathSegmentParsed { parsed = false break } } if parsed { return } } if path, err := ParsePath(pointer); err == nil { paths[pointer] = path } } func schemaSecretRoot(schema *schemaDescriptor, path Path) (Path, bool) { if schema == nil || schema.root == nil || !path.valid() { return Path{}, false } current := schema.root walked := Path{} for _, segment := range path.segments { walked.segments = append(walked.segments, segment) var ok bool current, ok = terminalDescriptor(current) if !ok { return Path{}, false } switch current.kind { case descriptorStruct: if segment.kind == pathSegmentIndex { return Path{}, false } position, ok := current.fieldByName[segment.key] var field *fieldDescriptor if ok { field = ¤t.fields[position] } else { field = current.inlineMap } if field == nil { return Path{}, false } if field.secret { return walked.clone(), true } if field.inlineMap { current = field.descriptor.element } else { current = field.descriptor } case descriptorMap: if segment.kind == pathSegmentIndex { return Path{}, false } current = current.element case descriptorSlice, descriptorArray: if _, ok := schemaIndex(segment); !ok { return Path{}, false } current = current.element case descriptorDynamic: return Path{}, false default: return Path{}, false } } return Path{}, false } // Dynamic type replacement can change classification even when projected text // is equal. Validators must preserve this inspection metadata as well as values. func projectedSensitivity(root *document.Node) map[string]Path { result := make(map[string]Path) walkDocument(root, Path{}, func(path Path, node *document.Node) { if node.Sensitive { result[path.String()] = path.clone() } }) return result }