VYPR
Moderate severityNVD Advisory· Published Oct 22, 2025· Updated Oct 23, 2025

OpenBao and Vault Leak []byte Fields in Audit Logs

CVE-2025-62705

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.

PackageAffected versionsPatched versions
github.com/openbao/openbaoGo
< 0.0.0-20251022165510-cc2c476bac660.0.0-20251022165510-cc2c476bac66

Affected products

1

Patches

1
cc2c476bac66

Refactor audit log formatting due to copy (#2002)

https://github.com/openbao/openbaoAlexander ScheelOct 22, 2025via ghsa
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

News mentions

0

No linked articles in our index yet.