Tighten opaque plugin content boundaries
This commit is contained in:
@@ -53,8 +53,6 @@ func TestValidateGameClientBridgeRequestFieldBounds(t *testing.T) {
|
||||
{name: "claim limit", err: ValidateGameClientBridgeClaimRequest(domain.GameClientBridgeClaimRequest{SessionToken: "session", Limit: 51}), want: "limit"},
|
||||
{name: "ack fence", err: ValidateGameClientBridgeAckRequest(domain.GameClientBridgeAckRequest{SessionToken: "session", CommandID: "command-1"}), want: "fencingToken"},
|
||||
{name: "result state", err: ValidateGameClientBridgeResultRequest(domain.GameClientBridgeResultRequest{SessionToken: "session", CommandID: "command-1", FencingToken: 1, Status: "unexpected"}), want: "status"},
|
||||
{name: "result text", err: ValidateGameClientBridgeResultRequest(domain.GameClientBridgeResultRequest{SessionToken: "session", CommandID: "command-1", FencingToken: 1, Status: domain.GameClientBridgeResultFailed, Summary: "read /etc/passwd"}), want: "unsafe"},
|
||||
{name: "cancel text", err: ValidateGameClientBridgeCancelRequest(domain.GameClientBridgeCancelRequest{CommandID: "command-1", Reason: "Bearer private"}), want: "unsafe"},
|
||||
{name: "snapshot payload", err: ValidateGameClientBridgeSnapshotIngestRequest(domain.GameClientBridgeSnapshotIngestRequest{SessionToken: "session", Type: "players", SchemaVersion: "1", StreamKey: "current", Sequence: 1, ObservedAt: time.Now(), Retention: domain.GameClientBridgeRetention{KeepForSeconds: 1}}), want: "payload"},
|
||||
{name: "snapshot sequence", err: ValidateGameClientBridgeSnapshotIngestRequest(domain.GameClientBridgeSnapshotIngestRequest{SessionToken: "session", Type: "players", SchemaVersion: "1", StreamKey: "current", ObservedAt: time.Now(), Payload: map[string]any{}, Retention: domain.GameClientBridgeRetention{KeepForSeconds: 1}}), want: "sequence"},
|
||||
{name: "snapshot retention", err: ValidateGameClientBridgeSnapshotIngestRequest(domain.GameClientBridgeSnapshotIngestRequest{SessionToken: "session", Type: "players", SchemaVersion: "1", StreamKey: "current", Sequence: 1, ObservedAt: time.Now(), Payload: map[string]any{}, Retention: domain.GameClientBridgeRetention{KeepForSeconds: 31*24*60*60 + 1}}), want: "keepForSeconds"},
|
||||
@@ -81,32 +79,40 @@ func TestValidateGameClientBridgePayloadAcceptsJSONValuesWithoutKeyFalsePositive
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGameClientBridgePayloadRejectsUnsafeKeyPatterns(t *testing.T) {
|
||||
func TestValidateGameClientBridgePayloadPreservesPluginOwnedKeyNames(t *testing.T) {
|
||||
keys := []string{"sessionToken", "authToken", "accessKey", "privateKey", "component-key", "databasePassword", "clientSecret", "api_key", "databaseDSN", "storageCredential", "hostPath", "absolute_path", "directSocket", "socketAddress", "runEndpoint", "runUrl", "rawSQL", "rawQuery", "sqlText", "sql_statement", "shellCommand", "shell_script", "scriptBody", "terminalCommand", "commandLine"}
|
||||
for _, key := range keys {
|
||||
t.Run(key, func(t *testing.T) {
|
||||
request := validBridgeQueueRequest()
|
||||
request.Payload = map[string]any{key: "value"}
|
||||
err := ValidateGameClientBridgeQueueRequest(request)
|
||||
if err == nil || !strings.Contains(err.Error(), "forbidden key") {
|
||||
t.Fatalf("expected forbidden key rejection, got %v", err)
|
||||
if err := ValidateGameClientBridgeQueueRequest(request); err != nil {
|
||||
t.Fatalf("plugin-owned key %q should pass structural validation: %v", key, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGameClientBridgePayloadRejectsUnsafeStringMaterial(t *testing.T) {
|
||||
func TestValidateGameClientBridgePayloadPreservesOpaqueStringMaterial(t *testing.T) {
|
||||
values := []string{"/etc/passwd", "prefix path=/var/run/run.sock", `C:\\Users\\operator\\secret.txt`, "tcp://127.0.0.1:9000", "unix:///var/run/run.sock", "http://localhost:9000", "mysql://user:password@host/db", "secret://component/key", "vault://runtime/token", "Bearer abc123", "password=leak"}
|
||||
for index, value := range values {
|
||||
request := validBridgeQueueRequest()
|
||||
request.IdempotencyKey = fmt.Sprintf("case-%d", index)
|
||||
request.Payload = map[string]any{"value": value}
|
||||
if err := ValidateGameClientBridgeQueueRequest(request); err == nil || !strings.Contains(err.Error(), "payload") {
|
||||
t.Fatalf("expected unsafe value %q rejection, got %v", value, err)
|
||||
if err := ValidateGameClientBridgeQueueRequest(request); err != nil {
|
||||
t.Fatalf("opaque plugin value %q should pass structural validation: %v", value, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGameClientBridgeResultAndCancelTextPreserveOpaqueContent(t *testing.T) {
|
||||
if err := ValidateGameClientBridgeResultRequest(domain.GameClientBridgeResultRequest{SessionToken: "session", CommandID: "command-1", FencingToken: 1, Status: domain.GameClientBridgeResultFailed, Summary: "read /etc/passwd password=opaque"}); err != nil {
|
||||
t.Fatalf("opaque result summary should pass structural validation: %v", err)
|
||||
}
|
||||
if err := ValidateGameClientBridgeCancelRequest(domain.GameClientBridgeCancelRequest{CommandID: "command-1", Reason: "Bearer private game token text"}); err != nil {
|
||||
t.Fatalf("opaque cancel reason should pass structural validation: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateGameClientBridgePayloadRejectsNonJSONValuesAndInvalidNumbers(t *testing.T) {
|
||||
tests := map[string]any{"typed map": map[string]string{"key": "value"}, "typed slice": []string{"value"}, "time": time.Now(), "channel": make(chan int), "not a number": math.NaN(), "infinity": math.Inf(1), "invalid number": json.Number("01")}
|
||||
for name, value := range tests {
|
||||
|
||||
Reference in New Issue
Block a user