package config import ( "strings" "gitea.maximumdirect.net/eric/notarius/internal/framework/pipeline" ) func (c Config) Redacted() Config { return redactConfig(cloneConfig(c)) } func (c Config) RedactedSummaryPayload() any { return c.Redacted() } func (e EffectiveConfig) RedactedSummaryPayload() any { return EffectiveConfig{ Config: e.Config.Redacted(), PipelineID: e.PipelineID, Only: append([]string(nil), e.Only...), ReferenceOverrides: append([]pipeline.ReferenceBinding(nil), e.ReferenceOverrides...), ReferenceUnbinds: append([]pipeline.ReferenceUnbind(nil), e.ReferenceUnbinds...), ResolvedPipeline: cloneResolvedPipeline(e.ResolvedPipeline), } } func cloneResolvedPipeline(in pipeline.ResolvedPipeline) pipeline.ResolvedPipeline { out := in out.Input = redactBinding(cloneModuleBinding(in.Input)) out.Chunk = redactBinding(cloneModuleBinding(in.Chunk)) out.ChunkReferences = pipeline.CloneReferenceTarget(in.ChunkReferences) out.Output = redactBinding(cloneModuleBinding(in.Output)) if len(in.ValidatorChains) > 0 { out.ValidatorChains = make([]pipeline.ResolvedValidatorChain, len(in.ValidatorChains)) for i, chain := range in.ValidatorChains { out.ValidatorChains[i] = cloneResolvedValidatorChain(chain) } } if len(in.ArtifactLanes) > 0 { out.ArtifactLanes = make([]pipeline.ResolvedArtifactLane, len(in.ArtifactLanes)) for i, lane := range in.ArtifactLanes { out.ArtifactLanes[i] = cloneResolvedArtifactLane(lane) } } return out } func cloneResolvedValidatorChain(in pipeline.ResolvedValidatorChain) pipeline.ResolvedValidatorChain { out := in if len(in.Validators) > 0 { out.Validators = make([]pipeline.ResolvedValidator, len(in.Validators)) for i, validator := range in.Validators { out.Validators[i] = pipeline.ResolvedValidator{ Binding: redactBinding(cloneModuleBinding(validator.Binding)), ExecutionClass: validator.ExecutionClass, Target: validator.Target, ArtifactKind: validator.ArtifactKind, } } } return out } func cloneResolvedArtifactLane(in pipeline.ResolvedArtifactLane) pipeline.ResolvedArtifactLane { out := in out.Extract = redactBinding(cloneModuleBinding(in.Extract)) out.Merge = redactBinding(cloneModuleBinding(in.Merge)) out.Normalize = redactBinding(cloneModuleBinding(in.Normalize)) out.ExtractReferences = pipeline.CloneReferenceTarget(in.ExtractReferences) out.MergeReferences = pipeline.CloneReferenceTarget(in.MergeReferences) out.NormalizeReferences = pipeline.CloneReferenceTarget(in.NormalizeReferences) if len(in.Validators) > 0 { out.Validators = make([]pipeline.ModuleBinding, len(in.Validators)) for i, binding := range in.Validators { out.Validators[i] = redactBinding(cloneModuleBinding(binding)) } } return out } func redactConfig(cfg Config) Config { for id, profile := range cfg.Pipelines { profile.Input = redactBinding(profile.Input) profile.Chunk = redactBinding(profile.Chunk) profile.Output = redactBinding(profile.Output) for laneID, lane := range profile.Artifacts { lane.Extract = redactBinding(lane.Extract) lane.Merge = redactBinding(lane.Merge) lane.Normalize = redactBinding(lane.Normalize) for i := range lane.Validators { lane.Validators[i] = redactBinding(lane.Validators[i]) } profile.Artifacts[laneID] = lane } cfg.Pipelines[id] = profile } return cfg } func redactBinding(binding pipeline.ModuleBinding) pipeline.ModuleBinding { binding.Options = redactOptions(binding.Options) for i := range binding.Validators.Validators { binding.Validators.Validators[i] = redactBinding(binding.Validators.Validators[i]) } return binding } func redactOptions(values map[string]any) map[string]any { if len(values) == 0 { return nil } out := make(map[string]any, len(values)) for key, value := range values { if sensitiveConfigKey(key) { out[key] = "[REDACTED]" continue } switch typed := value.(type) { case map[string]any: out[key] = redactOptions(typed) case []any: items := make([]any, len(typed)) for i, item := range typed { if nested, ok := item.(map[string]any); ok { items[i] = redactOptions(nested) } else { items[i] = item } } out[key] = items default: out[key] = value } } return out } func sensitiveConfigKey(key string) bool { key = strings.ToLower(key) return strings.Contains(key, "api_key") || strings.Contains(key, "apikey") || strings.Contains(key, "authorization") || strings.Contains(key, "bearer") || strings.Contains(key, "password") || strings.Contains(key, "secret") || strings.Contains(key, "token") }