/ internal / memory / audit_test.go
audit_test.go
 1  package memory
 2  
 3  import (
 4  	"strings"
 5  	"testing"
 6  )
 7  
 8  // TestAudit_NeverContainsKey enforces the privacy invariant from spec §7:
 9  //
10  //	(a) no string field across the whole event payload contains the resolved
11  //	    API key bytes (substring assertion);
12  //	(b) any field describing key/endpoint state is a bool, not a string.
13  //
14  // This is the schema-shape gate that prevents a future bug where someone
15  // interpolates the api_key into an "error context" string by accident.
16  func TestAudit_NeverContainsKey(t *testing.T) {
17  	captured := []map[string]any{}
18  	a := AuditFunc(func(_ string, fields map[string]any) {
19  		captured = append(captured, fields)
20  	})
21  	key := "secret-API-KEY-do-not-leak-1234567890"
22  	fp := Fingerprint(key)
23  
24  	// Realistic event payloads matching the §7 audit-event list.
25  	a.Log("memory_tlm_missing", map[string]any{"tlm_path_set": false})
26  	a.Log("memory_cloud_misconfigured", map[string]any{"endpoint_resolved": false, "api_key_present": false})
27  	a.Log("memory_tenant_switch", map[string]any{"fingerprint": fp})
28  	a.Log("memory_sidecar_degraded", map[string]any{})
29  	a.Log("memory_reload_failed", map[string]any{"reason": "timeout"})
30  	a.Log("memory_response_decode_failed", map[string]any{"sub_code": "x"})
31  	a.Log("memory_bundle_install_failed", map[string]any{"reason": "sha256_mismatch", "path_sample": "data.bin"})
32  	a.Log("memory_bundle_unsafe_path", map[string]any{"path_sample": "../../../etc/passwd", "reason": "contains parent traversal"})
33  
34  	for _, p := range captured {
35  		for k, v := range p {
36  			if s, ok := v.(string); ok && strings.Contains(s, key) {
37  				t.Fatalf("api key leaked in field %q: %q", k, s)
38  			}
39  			// Boolean-only convention for key/endpoint state.
40  			switch k {
41  			case "api_key_present", "endpoint_resolved", "tlm_path_set":
42  				if _, ok := v.(bool); !ok {
43  					t.Fatalf("field %q must be bool, got %T", k, v)
44  				}
45  			}
46  		}
47  	}
48  }