mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-11 08:23:45 +00:00
fix(multiplayer): harden observability redaction
This commit is contained in:
@@ -1449,6 +1449,8 @@ The control plane now exports bounded Prometheus-compatible API request counters
|
|||||||
|
|
||||||
That local gate now also runs `go test -race ./...`, `go vet ./...`, and each declared domain fuzz target for a bounded 2-second interval, aligning the one-command gate with the separately recorded 8.46 verification requirements.
|
That local gate now also runs `go test -race ./...`, `go vet ./...`, and each declared domain fuzz target for a bounded 2-second interval, aligning the one-command gate with the separately recorded 8.46 verification requirements.
|
||||||
|
|
||||||
|
Observability redaction now adds content-aware protection on top of denylisted field names: bearer values, compact JWT-like strings, PEM material, and long opaque mixed alphanumeric values are redacted recursively through arbitrary nested maps and string slices. Unknown-key credential canaries pass without leaking; false-positive risk is limited to custom long opaque fields, while canonical correlation IDs remain outside the free-form field map.
|
||||||
|
|
||||||
The three declared domain fuzz targets have now each completed a bounded 4-second run (`FuzzQueueCreateDoesNotPanic`, `FuzzResultDigestIsDeterministic`, and `FuzzSyncEventApplicationDoesNotPanic`) with no failures; this closes the locally runnable fuzz portion of 8.46. Live Redis failover, further transaction races, and cloud/runtime gates remain explicitly unverified.
|
The three declared domain fuzz targets have now each completed a bounded 4-second run (`FuzzQueueCreateDoesNotPanic`, `FuzzResultDigestIsDeterministic`, and `FuzzSyncEventApplicationDoesNotPanic`) with no failures; this closes the locally runnable fuzz portion of 8.46. Live Redis failover, further transaction races, and cloud/runtime gates remain explicitly unverified.
|
||||||
|
|
||||||
The real ENet integration gate now passes with `GODOT_BIN=/Applications/Godot.app/Contents/MacOS/Godot bash scripts/verify_enet_integration.sh`, covering the `net`, `match-net`, `clock`, `lobby`, and `networked match` process scenarios. The default `GODOT_BIN` remains the portable `godot` PATH lookup for CI; this machine requires the explicit app-bundle path.
|
The real ENet integration gate now passes with `GODOT_BIN=/Applications/Godot.app/Contents/MacOS/Godot bash scripts/verify_enet_integration.sh`, covering the `net`, `match-net`, `clock`, `lobby`, and `networked match` process scenarios. The default `GODOT_BIN` remains the portable `godot` PATH lookup for CI; this machine requires the explicit app-bundle path.
|
||||||
|
|||||||
@@ -45,19 +45,57 @@ func redact(key string, value any) any {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch typed := value.(type) {
|
switch typed := value.(type) {
|
||||||
|
case string:
|
||||||
|
if looksLikeCredential(typed) {
|
||||||
|
return "[REDACTED]"
|
||||||
|
}
|
||||||
|
return typed
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
copy := make(map[string]any, len(typed))
|
copy := make(map[string]any, len(typed))
|
||||||
for key, value := range typed {
|
for key, value := range typed {
|
||||||
copy[key] = redact(key, value)
|
copy[key] = redact(key, value)
|
||||||
}
|
}
|
||||||
return copy
|
return copy
|
||||||
|
case map[string]string:
|
||||||
|
copy := make(map[string]string, len(typed))
|
||||||
|
for key, value := range typed {
|
||||||
|
redacted := redact(key, value)
|
||||||
|
copy[key] = redacted.(string)
|
||||||
|
}
|
||||||
|
return copy
|
||||||
case []any:
|
case []any:
|
||||||
copy := make([]any, len(typed))
|
copy := make([]any, len(typed))
|
||||||
for i, value := range typed {
|
for i, value := range typed {
|
||||||
copy[i] = redact("item", value)
|
copy[i] = redact("item", value)
|
||||||
}
|
}
|
||||||
return copy
|
return copy
|
||||||
|
case []string:
|
||||||
|
copy := make([]string, len(typed))
|
||||||
|
for i, value := range typed {
|
||||||
|
copy[i] = redact("item", value).(string)
|
||||||
|
}
|
||||||
|
return copy
|
||||||
default:
|
default:
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func looksLikeCredential(value string) bool {
|
||||||
|
trimmed := strings.TrimSpace(value)
|
||||||
|
if strings.HasPrefix(strings.ToLower(trimmed), "bearer ") || strings.Contains(trimmed, "-----BEGIN ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
parts := strings.Split(trimmed, ".")
|
||||||
|
if len(parts) == 3 && len(parts[0]) >= 8 && len(parts[1]) >= 8 && len(parts[2]) >= 8 {
|
||||||
|
return true // compact JWT-like credential
|
||||||
|
}
|
||||||
|
if len(trimmed) < 40 || strings.ContainsAny(trimmed, " \t\r\n") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
hasLetter, hasDigit := false, false
|
||||||
|
for _, ch := range trimmed {
|
||||||
|
hasLetter = hasLetter || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
|
||||||
|
hasDigit = hasDigit || (ch >= '0' && ch <= '9')
|
||||||
|
}
|
||||||
|
return hasLetter && hasDigit
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package observability
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -30,3 +31,20 @@ func TestEncodeRejectsUnnamedEvents(t *testing.T) {
|
|||||||
t.Fatal("unnamed event accepted")
|
t.Fatal("unnamed event accepted")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEncodeRedactsCredentialLookingValuesUnderUnknownKeys(t *testing.T) {
|
||||||
|
payload, err := Encode(Event{Event: "test", Fields: map[string]any{
|
||||||
|
"unexpected": "workload-secret-value-12345678901234567890",
|
||||||
|
"nested": map[string]string{"opaque": "Bearer should-not-appear"},
|
||||||
|
"items": []string{"eyJhbGciOiJIUzI1NiJ9.payload-value.signature-value"},
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
text := string(payload)
|
||||||
|
for _, secret := range []string{"workload-secret-value", "Bearer should-not-appear", "eyJhbGciOiJIUzI1NiJ9"} {
|
||||||
|
if strings.Contains(text, secret) {
|
||||||
|
t.Fatalf("credential leaked under unknown key: %s", payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user