OpenBao and Vault Leak []byte Fields in Audit Logs
Description
OpenBao is an open source identity-based secrets management system. Prior to version 2.4.2, OpenBao's audit log did not appropriately redact fields when relevant subsystems sent []byte response parameters rather than strings. This includes, but is not limited to sys/raw with use of encoding=base64, all data would be emitted unredacted to the audit log, and Transit, when performing a signing operation with a derived Ed25519 key, would emit public keys to the audit log. This issue has been patched in OpenBao 2.4.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/openbao/openbaoGo | < 0.0.0-20251022165510-cc2c476bac66 | 0.0.0-20251022165510-cc2c476bac66 |
Affected products
1Patches
1cc2c476bac66Refactor audit log formatting due to copy (#2002)
5 files changed · +233 −273
audit/format.go+14 −13 modified@@ -572,24 +572,25 @@ func NewTemporaryFormatter(format, prefix string) *AuditFormatter { return ret } -// doElideListResponseData performs the actual elision of list operation response data, once surrounding code has -// determined it should apply to a particular request. The data map that is passed in must be a copy that is safe to -// modify in place, but need not be a full recursive deep copy, as only top-level keys are changed. +// doElideListResponseData performs the actual elision of list operation +// response data, once surrounding code has determined it should apply to +// a particular request. The data map that is passed in must be a copy that +// is safe to modify in place, but need not be a full recursive deep copy, +// as only top-level keys are changed. // // See the documentation of the controlling option in FormatterConfig for more information on the purpose. func doElideListResponseData(data map[string]interface{}) { - doElideListResponseDataWithCopy(data, data) -} - -func doElideListResponseDataWithCopy(inputData map[string]interface{}, outputData map[string]interface{}) { - for k, v := range inputData { - if k == "keys" { - if vSlice, ok := v.([]string); ok { - outputData[k] = len(vSlice) + for k, v := range data { + switch k { + case "keys": + if vSlice, ok := v.([]interface{}); ok { + data[k] = len(vSlice) + } else if vSlice, ok := v.([]string); ok { + data[k] = len(vSlice) } - } else if k == "key_info" { + case "key_info": if vMap, ok := v.(map[string]interface{}); ok { - outputData[k] = len(vMap) + data[k] = len(vMap) } } }
audit/format_test.go+7 −9 modified@@ -47,18 +47,17 @@ func (fw *testingFormatWriter) Salt(ctx context.Context) (*salt.Salt, error) { // so that we can use assert.Equal to compare the expected and output values. func (fw *testingFormatWriter) hashExpectedValueForComparison(input map[string]interface{}) map[string]interface{} { // Copy input before modifying, since we may re-use the same data in another test - copied, err := getUnmarshaledCopy(input) + copiedAsMap, err := getUnmarshaledCopy(input) if err != nil { panic(err) } - copiedAsMap := copied.(map[string]interface{}) salter, err := fw.Salt(context.Background()) if err != nil { panic(err) } - err = hashMap(salter.GetIdentifiedHMAC, input, copiedAsMap, nil, false) + err = hashMap(salter.GetIdentifiedHMAC, copiedAsMap, nil, false) if err != nil { panic(err) } @@ -226,8 +225,8 @@ func TestElideListResponses(t *testing.T) { func fixupInputData(inputData map[string]interface{}) map[string]interface{} { // json marshalling/unmarshalling converts []string's into []interface{} - // this method returns a copy of the input data with that transformation - // so it can be checked against the results + // this method returns a copy of the input data with that transformation + // so it can be checked against the results newSlice := make([]interface{}, len(inputData["keys"].([]string))) for i, v := range inputData["keys"].([]string) { newSlice[i] = v @@ -239,13 +238,12 @@ func fixupInputData(inputData map[string]interface{}) map[string]interface{} { } // Because the elided real data doesn't get unmarshaled, it doesn't -// get converted to floats. But when the elided test-data is hashed +// get converted to floats. But when the elided test-data is hashed // by the tfw.hashExpectedValueForComparison() method, that method // doesn't handle elision and the elided ints get converted to floats. // -// This func corrects the issue with -// tfw.hashExpectedValueForComparison(), by converting the floats back -// to ints. +// This func corrects the issue with tfw.hashExpectedValueForComparison(), +// by converting the floats back to ints. func fixupElidedTestData(inputData map[string]interface{}) map[string]interface{} { for k, v := range inputData { if k == "keys" || k == "key_info" {
audit/hashstructure.go+74 −242 modified@@ -6,10 +6,9 @@ package audit import ( "encoding/json" "errors" + "fmt" "reflect" "slices" - "strconv" - "strings" "time" "github.com/mitchellh/copystructure" @@ -71,23 +70,24 @@ func HashRequest(salter *salt.Salt, in *logical.Request, HMACAccessor bool, nonH } if req.Data != nil { - copy, err := getUnmarshaledCopy(req.Data) + reqData, err := getUnmarshaledCopy(req.Data) if err != nil { return nil, err } - err = hashMap(fn, req.Data, copy.(map[string]interface{}), nonHMACDataKeys, false) + err = hashMap(fn, reqData, nonHMACDataKeys, false) if err != nil { return nil, err } - req.Data = copy.(map[string]interface{}) + + req.Data = reqData } return &req, nil } -func hashMap(fn func(string) string, origData map[string]interface{}, data map[string]interface{}, nonHMACDataKeys []string, elideListResponseData bool) error { - return HashStructure(origData, data, fn, nonHMACDataKeys, elideListResponseData) +func hashMap(fn func(string) string, data map[string]interface{}, nonHMACDataKeys []string, elideListResponseData bool) error { + return HashStructure(data, fn, nonHMACDataKeys, elideListResponseData) } // HashResponse returns a hashed copy of the logical.Request input. @@ -118,28 +118,32 @@ func HashResponse( } if resp.Data != nil { - copy, err := getUnmarshaledCopy(resp.Data) + respData, err := getUnmarshaledCopy(resp.Data) if err != nil { return nil, err } - mapCopy := copy.(map[string]interface{}) - if b, ok := mapCopy[logical.HTTPRawBody].([]byte); ok { - mapCopy[logical.HTTPRawBody] = string(b) + // When we JSON marshal resp.Data into respData, we base64 encode the + // raw response body. This breaks compatibility with earlier Vault + // versions, so revert to the direct string form here. + if b, ok := resp.Data[logical.HTTPRawBody].([]byte); ok { + respData[logical.HTTPRawBody] = string(b) } - // Processing list response data elision takes place at this point in the code for performance reasons: - // - take advantage of the deep copy of resp.Data that was going to be done anyway for hashing + // Processing list response data elision takes place at this point + // in the code for performance reasons: + // - take advantage of the deep copy of resp.Data that was going to + // be done anyway for hashing // - but elide data before potentially spending time hashing it if elideListResponseData { - doElideListResponseDataWithCopy(resp.Data, mapCopy) + doElideListResponseData(respData) } - err = hashMap(fn, resp.Data, mapCopy, nonHMACDataKeys, elideListResponseData) + err = hashMap(fn, respData, nonHMACDataKeys, elideListResponseData) if err != nil { return nil, err } - resp.Data = mapCopy + resp.Data = respData } if resp.WrapInfo != nil { @@ -157,7 +161,7 @@ func HashResponse( // This transformation inherently changes all structs to maps, which makes // each of the structs fields addressable through reflection in the copy, // (which is now a map). This will allow us to write into all fields. -func getUnmarshaledCopy(data interface{}) (interface{}, error) { +func getUnmarshaledCopy(data interface{}) (map[string]interface{}, error) { marshaledData, err := json.Marshal(data) if err != nil { return nil, err @@ -193,24 +197,16 @@ func HashWrapInfo(salter *salt.Salt, in *wrapping.ResponseWrapInfo, HMACAccessor // HashStructure takes an interface and hashes all the values within // the structure. Only _values_ are hashed: keys of objects are not. -// Note that the function takes both the original data structure and -// an unmarshaled json copy. The hashed values are written to the copy -// but because the json transform has changed the type info the original -// must also be walked to get that type info. // -// The original is walked with the reflectwalk.Walk() method below. -// That method leaves a trail in w.loc[]/w.csKey[] which is then used -// by getValueFromCopy() to walk the copy. +// The interface is walked with the reflectwalk.Walk() method below. // // For the HashCallback, see the built-in HashCallbacks below. -func HashStructure(original interface{}, copy interface{}, cb HashCallback, - ignoredKeys []string, elideListResponseData bool, -) error { +func HashStructure(data interface{}, cb HashCallback, ignoredKeys []string, elideListResponseData bool) error { walker := &hashWalker{ - UnmarshalledCopy: copy, Callback: cb, - IgnoredKeys: ignoredKeys, ElideListResponseData: elideListResponseData, + Callback: cb, + IgnoredKeys: ignoredKeys, } - return reflectwalk.Walk(original, walker) + return reflectwalk.Walk(data, walker) } // HashCallback is the callback called for HashStructure to hash @@ -225,36 +221,44 @@ type hashWalker struct { // to be hashed. If there is an error, walking will be halted // immediately and the error returned. Callback HashCallback - // IgnoreKeys are the keys that wont have the HashCallback applied + + // IgnoreKeys are the keys that wont have the HashCallback applied. IgnoredKeys []string + // MapElem appends the key itself (not the reflect.Value) to key. // The last element in key is the most recently entered map key. // Since Exit pops the last element of key, only nesting to another // structure increases the size of this slice. - key []string - lastValue reflect.Value + // + // Key is not updated for non-maps; this allows IgnoredKeys to + // reference the last map key and ignore intermediate slices. + key []string + // Enter appends to loc and exit pops loc. The last element of loc is thus // the current location. loc []reflectwalk.Location - // Map, Struct and Slice append to cs, Exit pops the last element off cs. + + // Map and Slice append to cs, Exit pops the last element off cs so length + // is only impacted by maximum object depth. + // // The last element in cs is the most recently entered map or slice. cs []reflect.Value - // MapElem, StructField and SliceElem append to csKey. The last element in csKey is the + + // MapElem and SliceElem append to csKey. The last element in csKey is the // most recently entered key or slice index. Since Exit pops the last // element of csKey, only nesting to another structure increases the size of // this slice. - csKey []reflect.Value - UnmarshalledCopy interface{} - ElideListResponseData bool + csKey []reflect.Value } -// hashTimeType stores a pre-computed reflect.Type for a time.Time so -// we can quickly compare in hashWalker.Struct. We create an empty/invalid -// time.Time{} so we don't need to incur any additional startup cost vs. -// Now() or Unix(). -var hashTimeType = reflect.TypeOf(time.Time{}) - func (w *hashWalker) Enter(loc reflectwalk.Location) error { + switch loc { + case reflectwalk.Struct: + return errors.New("unexpected struct remaining in JSON decoded value") + case reflectwalk.Array: + return errors.New("unexpected array remaining in JSON decoded value") + } + w.loc = append(w.loc, loc) return nil } @@ -268,11 +272,6 @@ func (w *hashWalker) Exit(loc reflectwalk.Location) error { case reflectwalk.MapValue: w.key = w.key[:len(w.key)-1] w.csKey = w.csKey[:len(w.csKey)-1] - case reflectwalk.Struct: - w.cs = w.cs[:len(w.cs)-1] - case reflectwalk.StructField: - w.key = w.key[:len(w.key)-1] - w.csKey = w.csKey[:len(w.csKey)-1] case reflectwalk.Slice: w.cs = w.cs[:len(w.cs)-1] case reflectwalk.SliceElem: @@ -288,17 +287,12 @@ func (w *hashWalker) Map(m reflect.Value) error { } func (w *hashWalker) MapElem(m, k, v reflect.Value) error { - // Json marshalling converts int keys to strings, so - // we need to handle those here. - if _, ok := k.Interface().(int); ok { - kString := strconv.FormatInt(k.Int(), 10) - w.csKey = append(w.csKey, reflect.ValueOf(kString)) - w.key = append(w.key, kString) - return nil + if k.Type().Kind() != reflect.String { + return fmt.Errorf("unknown map key type: %v", k.Type().String()) } + w.csKey = append(w.csKey, k) w.key = append(w.key, k.String()) - w.lastValue = v return nil } @@ -312,69 +306,6 @@ func (w *hashWalker) SliceElem(i int, elem reflect.Value) error { return nil } -func (w *hashWalker) Struct(v reflect.Value) error { - // We are looking for time values. If it isn't one, handle it later. - if v.Type() != hashTimeType { - w.cs = append(w.cs, v) - return nil - } - - if len(w.loc) < 3 { - // The last element of w.loc is reflectwalk.Struct, by definition. - // If len(w.loc) < 3 that means hashWalker.Walk was given a struct - // value and this is the very first step in the walk, and we don't - // currently support structs as inputs, - return errors.New("structs as direct inputs not supported") - } - - // Second to last element of w.loc is location that contains this struct. - switch w.loc[len(w.loc)-2] { - case reflectwalk.MapValue: - // Create a string value of the time. IMPORTANT: this must never change - // across Vault versions or the hash value of equivalent time.Time will - // change. - strVal := v.Interface().(time.Time).Format(time.RFC3339Nano) - - // Set the map value to the string instead of the time.Time object - m := w.getMapFromCopy() - mk := w.csKey[len(w.cs)-1].String() - m[mk] = strVal - case reflectwalk.SliceElem: - // Create a string value of the time. IMPORTANT: this must never change - // across Vault versions or the hash value of equivalent time.Time will - // change. - strVal := v.Interface().(time.Time).Format(time.RFC3339Nano) - - // Set the map value to the string instead of the time.Time object - s := w.getSliceFromCopy() - si := int(w.csKey[len(w.cs)-1].Int()) - s[si] = strVal - } - - // Skip this entry so that we don't walk the struct, but - // append it to w.cs so that it gets properly handled on - // Exit() - w.cs = append(w.cs, v) - return reflectwalk.SkipEntry -} - -// Update the name of the field if it has a json struct tag -func (w *hashWalker) StructField(s reflect.StructField, v reflect.Value) error { - if !s.IsExported() { - return reflectwalk.SkipEntry - } - name := s.Name - if tag := s.Tag.Get("json"); tag != "" { - parts := strings.Split(tag, ",") - if parts[0] != "" { - name = parts[0] - } - } - w.csKey = append(w.csKey, reflect.ValueOf(name)) - w.key = append(w.key, name) - return nil -} - // Primitive calls Callback to transform strings in-place, except for map keys. // Strings hiding within interfaces are also transformed. func (w *hashWalker) Primitive(v reflect.Value) error { @@ -395,141 +326,42 @@ func (w *hashWalker) Primitive(v reflect.Value) error { return nil } - // See if the current key is part of the ignored keys - currentKey := w.key[len(w.key)-1] - if slices.Contains(w.IgnoredKeys, currentKey) { + value := v.String() + + // Marshaling a time in an object will result in a RFC3339 string + // decodable by UnmarshalText. When this does not return an error, + // we know we strictly have a valid timestamp and nothing else. + var t time.Time + if err := t.UnmarshalText([]byte(value)); err == nil { return nil } - // The copy does not have elided fields so don't - // try to overwrite them. - if w.isElided() { + // See if the current key is part of the ignored keys; notably, this may + // be some child ancestor of the current reference. Consider: + // + // map[string]interface{}{ + // "ignored": []string{ "<we-are-here>" }, + // } + currentKey := w.key[len(w.key)-1] + if slices.Contains(w.IgnoredKeys, currentKey) { return nil } - replaceVal := w.Callback(v.String()) + replacement := w.Callback(value) + replaceVal := reflect.ValueOf(replacement) switch w.loc[len(w.loc)-1] { case reflectwalk.MapValue: - fallthrough - case reflectwalk.StructField: - // If we're in a map, then the only way to set a map value is - // to set it directly. - m := w.getMapFromCopy() - mk := w.csKey[len(w.cs)-1].String() - m[mk] = replaceVal + m := w.cs[len(w.cs)-1] + mk := w.csKey[len(w.cs)-1] + m.SetMapIndex(mk, replaceVal) case reflectwalk.SliceElem: - s := w.getSliceFromCopy() + s := w.cs[len(w.cs)-1] si := int(w.csKey[len(w.cs)-1].Int()) - s[si] = replaceVal - + s.Index(si).Set(replaceVal) default: - panic("Found unsupported value.") + return fmt.Errorf("reached HMAC value in object of type %v", w.loc[len(w.loc)-1]) } return nil } - -// get current value from copy to be written to. -func (w *hashWalker) getValueFromCopy() interface{} { - size := len(w.cs) - currentValue := w.UnmarshalledCopy - startKey := 2 // First key in w.csKey maps to w.loc[2] - keyFactor := 2 // Each key in w.csKey is every other entry in w.loc - for i := 0; i < size-1; i++ { - switch w.loc[startKey+(keyFactor*i)] { - case reflectwalk.MapValue: - fallthrough - case reflectwalk.StructField: - currentValue = getMap(currentValue)[w.csKey[i].String()] - case reflectwalk.SliceElem: - index := w.csKey[i].Int() - currentValue = getSlice(currentValue)[int(index)] - default: - panic("invalid location") - } - } - return currentValue -} - -func (w *hashWalker) getMapFromCopy() map[string]interface{} { - return getMap(w.getValueFromCopy()) -} - -func getMap(inputData interface{}) map[string]interface{} { - var value map[string]interface{} - if v, ok := inputData.(map[string]interface{}); ok { - value = v - } else { - panic("bad map") - } - return value -} - -func (w *hashWalker) getSliceFromCopy() []interface{} { - return getSlice(w.getValueFromCopy()) -} - -func getSlice(inputData interface{}) []interface{} { - var value []interface{} - if v, ok := inputData.([]interface{}); ok { - value = v - } else { - panic("bad slice") - } - return value -} - -// Skip elided data because they don't exist in the -// UnmarshalledCopy. This code is based on: -// audit.doElideListResponseDataWithCopy() -// if that method changes, this one will have to as well -func (w *hashWalker) isElided() bool { - if !w.ElideListResponseData { - return false - } - - currentLoc := len(w.loc) - 1 - currentCs := len(w.cs) - 1 - currentCsKey := len(w.csKey) - 1 - - if currentLoc <= 3 { - return false - } - - // Based on audit.doElideListResponseDataWithCopy - // we are lookinfor a map[string]interface{} which - // contains a "keys" field of type []string - // or a "key_info" field of type map[string]interface{} - if w.loc[currentLoc-3] != reflectwalk.Map || - w.loc[currentLoc-2] != reflectwalk.MapValue { - return false - } - - m := w.cs[currentCs-1] - mk := w.csKey[currentCsKey-1] - k := mk.String() - v := m.MapIndex(mk) - - // a "keys" field of type []string? - if w.loc[currentLoc-1] == reflectwalk.Slice && - w.loc[currentLoc] == reflectwalk.SliceElem && - k == "keys" { - _, vOk := v.Interface().([]string) - if vOk { - return true - } - } - - // a "key_info" field of type map[string]interface{} ? - if w.loc[currentLoc-1] == reflectwalk.Map && - w.loc[currentLoc] == reflectwalk.MapValue && - k == "key_info" { - _, vOk := v.Interface().(map[string]interface{}) - if vOk { - return true - } - } - - return false -}
audit/hashstructure_test.go+132 −9 modified@@ -287,6 +287,50 @@ func TestHashResponse(t *testing.T) { []string{"read_json"}, true, }, + { + &logical.Response{ + Data: map[string]interface{}{ + "foo": map[string]interface{}{ + "first": testTopicPermission{Write: "bar", Read: "baz"}, + "second": map[string]interface{}{ + "nested": testTopicPermission{Write: "war", Read: "waz"}, + }, + }, + }, + WrapInfo: &wrapping.ResponseWrapInfo{ + TTL: 60, + Token: "bar", + Accessor: "flimflam", + CreationTime: now, + WrappedAccessor: "bar", + }, + }, + &logical.Response{ + Data: map[string]interface{}{ + "foo": map[string]interface{}{ + "first": map[string]interface{}{ + "write_json": "bar", + "read_json": "hmac-sha256:57fe23dcea29b442ce536b9486b53999513079c8850b2c4ac83bb48529a00bfe", + }, + "second": map[string]interface{}{ + "nested": map[string]interface{}{ + "write_json": "war", + "read_json": "hmac-sha256:5c69f2a46b323680e94d5a0f50b3347a5dfeb8382db7ff38dfaae1516f8a142c", + }, + }, + }, + }, + WrapInfo: &wrapping.ResponseWrapInfo{ + TTL: 60, + Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", + Accessor: "hmac-sha256:7c9c6fe666d0af73b3ebcfbfabe6885015558213208e6635ba104047b22f6390", + CreationTime: now, + WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", + }, + }, + []string{"write_json"}, + true, + }, // Confirm int keys are converted to string keys { &logical.Response{ @@ -354,6 +398,85 @@ func TestHashResponse(t *testing.T) { []string{"baz"}, true, }, + { + &logical.Response{ + Data: map[string]interface{}{ + "key_info": map[string]interface{}{ + "random_string": map[string]interface{}{ + "name": "test", + "num_member_entities": 0, + "num_parent_groups": 0, + "deeply_nested_map": map[string]interface{}{ + "random_array": []string{"item", "another_item"}, + }, + }, + }, + "keys": "random_string", + }, + WrapInfo: &wrapping.ResponseWrapInfo{ + TTL: 60, + Token: "bar", + Accessor: "flimflam", + CreationTime: now, + WrappedAccessor: "bar", + }, + }, + &logical.Response{ + Data: map[string]interface{}{ + "key_info": map[string]interface{}{ + "random_string": map[string]interface{}{ + "deeply_nested_map": map[string]interface{}{ + "random_array": []interface{}{ + "hmac-sha256:5a4325952fec282c8dbfd0242cca5d018a210bb629e9ebc9278f58b5f4d73db1", + "hmac-sha256:0176bac06c07b7ccb59bda70be3cf50f5560d25328bf8043796d24be71177d2d", + }, + }, + "name": "hmac-sha256:3a5c1437614283a4c557670f3196c56d34dbc5109f2bf3db73e631cb1370a4e2", + "num_member_entities": float64(0), + "num_parent_groups": float64(0), + }, + }, + "keys": "hmac-sha256:c735e47de746c4b6607851f5aa107ebac9c53d8af0c807009c150672d6388609", + }, + WrapInfo: &wrapping.ResponseWrapInfo{ + TTL: 60, + Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", + Accessor: "hmac-sha256:7c9c6fe666d0af73b3ebcfbfabe6885015558213208e6635ba104047b22f6390", + CreationTime: now, + WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", + }, + }, + []string{}, + true, + }, + { + &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPRawBody: []byte("Response"), + }, + WrapInfo: &wrapping.ResponseWrapInfo{ + TTL: 60, + Token: "bar", + Accessor: "flimflam", + CreationTime: now, + WrappedAccessor: "bar", + }, + }, + &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPRawBody: "hmac-sha256:cf0faf58d6106e1f46cdfaf93353ae0fe08b21948de64a402ae9b77dbd9b07d1", + }, + WrapInfo: &wrapping.ResponseWrapInfo{ + TTL: 60, + Token: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", + Accessor: "hmac-sha256:7c9c6fe666d0af73b3ebcfbfabe6885015558213208e6635ba104047b22f6390", + CreationTime: now, + WrappedAccessor: "hmac-sha256:f9320baf0249169e73850cd6156ded0106e2bb6ad8cab01b7bbbebe6d1065317", + }, + }, + []string{}, + true, + }, } inmemStorage := &logical.InmemStorage{} @@ -375,7 +498,7 @@ func TestHashResponse(t *testing.T) { t.Fatalf("err: %s\n\n%s", err, input) } if diff := deep.Equal(out, tc.Output); len(diff) > 0 { - t.Fatalf("bad:\nInput:\n%s\nDiff:\n%#v", input, diff) + t.Fatalf("bad:\nOutput:\n%#v\nExpected:\n%#v\nDiff:\n%#v", tc.Output, out, diff) } } } @@ -407,15 +530,15 @@ func TestHashWalker(t *testing.T) { } for _, tc := range cases { - copy, _ := getUnmarshaledCopy(tc.Input) - err := HashStructure(tc.Input, copy, func(string) string { + data, _ := getUnmarshaledCopy(tc.Input) + err := HashStructure(data, func(string) string { return replaceText }, nil, false) if err != nil { t.Fatalf("err: %s\n\n%#v", err, tc.Input) } - if !reflect.DeepEqual(copy, tc.Output) { - t.Fatalf("bad:\n\n%#v\n\n%#v", copy, tc.Output) + if !reflect.DeepEqual(data, tc.Output) { + t.Fatalf("bad:\n\n%#v\n\n%#v", data, tc.Output) } } } @@ -449,15 +572,15 @@ func TestHashWalker_TimeStructs(t *testing.T) { } for _, tc := range cases { - copy, _ := getUnmarshaledCopy(tc.Input) - err := HashStructure(tc.Input, copy, func(s string) string { + data, _ := getUnmarshaledCopy(tc.Input) + err := HashStructure(data, func(s string) string { return s + replaceText }, nil, false) if err != nil { t.Fatalf("err: %v\n\n%#v", err, tc.Input) } - if !reflect.DeepEqual(copy, tc.Output) { - t.Fatalf("bad:\n\n%#v\n\n%#v", copy, tc.Output) + if !reflect.DeepEqual(data, tc.Output) { + t.Fatalf("bad:\n\n%#v\n\n%#v", data, tc.Output) } } }
changelog/2002.txt+6 −0 added@@ -0,0 +1,6 @@ +```release-note:security +audit: redact `HTTPRawBody` response parameter in audit logs; CVE-2025-62513 / GHSA-ghfh-fmx4-26h8. +``` +```release-note:security +audit: redact `[]byte` type response parameters in audit logs; GHSA-rc54-2g2c-g36g. +```
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-rc54-2g2c-g36gghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-62705ghsaADVISORY
- github.com/openbao/openbao/commit/cc2c476bac66e1d94776c2629793daec3af625f8ghsax_refsource_MISCWEB
- github.com/openbao/openbao/security/advisories/GHSA-rc54-2g2c-g36gghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.