package config import ( "reflect" "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 (e EffectiveConfig) RedactedResolvedPipelinePayload() pipeline.ResolvedPipeline { return 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 } out[key] = redactOptionValue(value) } return out } func redactOptionValue(value any) any { if value == nil { return nil } reflected := reflect.ValueOf(value) switch reflected.Kind() { case reflect.Map: if reflected.Type().Key().Kind() != reflect.String { return value } if reflected.IsNil() { return nil } out := make(map[string]any, reflected.Len()) iterator := reflected.MapRange() for iterator.Next() { key := iterator.Key().String() if sensitiveConfigKey(key) { out[key] = "[REDACTED]" continue } out[key] = redactOptionValue(iterator.Value().Interface()) } return out case reflect.Slice: if reflected.IsNil() { return nil } if reflected.Type().Elem().Kind() == reflect.Uint8 { out := reflect.MakeSlice(reflected.Type(), reflected.Len(), reflected.Len()) reflect.Copy(out, reflected) return out.Interface() } fallthrough case reflect.Array: items := make([]any, reflected.Len()) for i := 0; i < reflected.Len(); i++ { items[i] = redactOptionValue(reflected.Index(i).Interface()) } return items default: return value } } 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") }