@@ -10,6 +10,7 @@ import (
1010 "reflect"
1111 "regexp"
1212 "strings"
13+ "unsafe"
1314
1415 "github.com/mitchellh/reflectwalk"
1516)
@@ -48,6 +49,16 @@ type TrustedValue interface {
4849 IsTrustedValue ()
4950}
5051
52+ type TrustedValueWrap struct {
53+ Value any
54+ }
55+
56+ func (TrustedValueWrap ) IsTrustedValue () {}
57+
58+ func (t TrustedValueWrap ) MarshalJSON () ([]byte , error ) {
59+ return json .Marshal (t .Value )
60+ }
61+
5162// Scrubber defines the interface for a scrubber, which can sanitise various types of data.
5263// The scrubbing process involves removing or replacing sensitive data to prevent it from being exposed.
5364//
@@ -86,6 +97,8 @@ type Scrubber interface {
8697 // }
8798 //
8899 Struct (val any ) error
100+
101+ DeepCopyStruct (val any ) any
89102}
90103
91104// Default is the default scrubber consumers of this package should use
@@ -189,6 +202,129 @@ func (s *scrubberImpl) Struct(val any) error {
189202 return nil
190203}
191204
205+ func (s * scrubberImpl ) deepCopyStruct (fieldName string , src reflect.Value , scrubTag string ) reflect.Value {
206+ if src .Kind () == reflect .Ptr && src .IsNil () {
207+ return reflect .New (src .Type ()).Elem ()
208+ }
209+
210+ if src .Kind () == reflect .String {
211+ dst := reflect .New (src .Type ())
212+ var (
213+ setExplicitValue bool
214+ explicitValue string
215+ )
216+ switch scrubTag {
217+ case "ignore" :
218+ return dst
219+ case "hash" :
220+ setExplicitValue = true
221+ explicitValue = SanitiseHash (src .String ())
222+ case "redact" :
223+ setExplicitValue = true
224+ explicitValue = SanitiseRedact (src .String ())
225+ }
226+
227+ if setExplicitValue {
228+ dst .Elem ().SetString (explicitValue )
229+ } else {
230+ sanitisatiser := s .getSanitisatiser (fieldName )
231+ if sanitisatiser != nil {
232+ dst .Elem ().SetString (sanitisatiser (src .String ()))
233+ } else {
234+ dst .Elem ().SetString (s .Value (src .String ()))
235+ }
236+ }
237+ if ! dst .CanInterface () {
238+ return dst
239+ }
240+ return dst .Elem ()
241+ }
242+
243+ switch src .Kind () {
244+
245+ case reflect .Struct :
246+ dst := reflect .New (src .Type ())
247+ t := src .Type ()
248+
249+ for i := 0 ; i < t .NumField (); i ++ {
250+ f := t .Field (i )
251+ srcValue := src .Field (i )
252+ dstValue := dst .Elem ().Field (i )
253+
254+ if ! srcValue .CanInterface () {
255+ dstValue = reflect .NewAt (dstValue .Type (), unsafe .Pointer (dstValue .UnsafeAddr ())).Elem ()
256+
257+ if ! srcValue .CanAddr () {
258+ switch {
259+ case srcValue .CanInt ():
260+ dstValue .SetInt (srcValue .Int ())
261+ case srcValue .CanUint ():
262+ dstValue .SetUint (srcValue .Uint ())
263+ case srcValue .CanFloat ():
264+ dstValue .SetFloat (srcValue .Float ())
265+ case srcValue .CanComplex ():
266+ dstValue .SetComplex (srcValue .Complex ())
267+ case srcValue .Kind () == reflect .Bool :
268+ dstValue .SetBool (srcValue .Bool ())
269+ }
270+
271+ continue
272+ }
273+
274+ srcValue = reflect .NewAt (srcValue .Type (), unsafe .Pointer (srcValue .UnsafeAddr ())).Elem ()
275+ }
276+
277+ tagValue := f .Tag .Get ("scrub" )
278+ copied := s .deepCopyStruct (f .Name , srcValue , tagValue )
279+ dstValue .Set (copied )
280+ }
281+ return dst .Elem ()
282+
283+ case reflect .Map :
284+ dst := reflect .MakeMap (src .Type ())
285+ keys := src .MapKeys ()
286+ for i := 0 ; i < src .Len (); i ++ {
287+ mValue := src .MapIndex (keys [i ])
288+ dst .SetMapIndex (keys [i ], s .deepCopyStruct (keys [i ].String (), mValue , "" ))
289+ }
290+ return dst
291+
292+ case reflect .Slice :
293+ dst := reflect .MakeSlice (src .Type (), src .Len (), src .Cap ())
294+ for i := 0 ; i < src .Len (); i ++ {
295+ dst .Index (i ).Set (s .deepCopyStruct (fieldName , src .Index (i ), "" ))
296+ }
297+ return dst
298+
299+ case reflect .Array :
300+ if src .Len () == 0 {
301+ return src // can not access to src.Index(0)
302+ }
303+
304+ dst := reflect .New (src .Type ()).Elem ()
305+ for i := 0 ; i < src .Len (); i ++ {
306+ dst .Index (i ).Set (s .deepCopyStruct (fieldName , src .Index (i ), "" ))
307+ }
308+ return dst
309+
310+ case reflect .Ptr :
311+ dst := reflect .New (src .Elem ().Type ())
312+ copied := s .deepCopyStruct (fieldName , src .Elem (), scrubTag )
313+ dst .Elem ().Set (copied )
314+ return dst
315+
316+ default :
317+ dst := reflect .New (src .Type ())
318+ dst .Elem ().Set (src )
319+ return dst .Elem ()
320+ }
321+ }
322+
323+ // Struct implements Scrubber
324+ func (s * scrubberImpl ) DeepCopyStruct (val any ) any {
325+ return s .deepCopyStruct ("" , reflect .ValueOf (val ), "" ).Interface ()
326+ }
327+
192328func (s * scrubberImpl ) scrubJsonObject (val map [string ]interface {}) error {
193329 // fix https://github.com/gitpod-io/security/issues/64
194330 name , _ := val ["name" ].(string )
0 commit comments