fix(CLI-40,CLI-41,CLI-44): exact-secret scrub, uniform malformed-reply contract, Go terminal-error mislabel
CLI-40: port the exact-secret credential scrub to Rust/Java/.NET (Go/Python
already did it). AuthenticateUser/WriteSecured(2) helpers now redact the exact
caller-supplied secret from any surfaced error, as defense-in-depth on top of the
by-construction guarantee. Rust hand-writes a redacting Debug (derived Debug would
leak the reply); Java/.NET rebuild the same exception type with the redacted
message and do not carry the secret-bearing original forward (so ToString/stack
traces stay clean too).
CLI-41: uniform malformed-reply contract for AuthenticateUser/ArchestrAUserToId/
AddBufferedItem across all five clients — typed payload, else a present int32
return_value, else a typed malformed-reply error. Fixes Go/Java silent-0, .NET
NRE, and Rust's own internal inconsistency.
CLI-44: the Go event goroutine's Recv-error path now uses a non-blocking
sendTerminalEventResult on the reserved slot, so a genuine terminal stream error
is reported as itself instead of being mislabeled ErrSlowConsumer under overflow.
Riders from the CLI-37/38 review: (a) .NET ToDiagnosticSummary and Python
_mxaccess_message surface the raw success member (diagnostics-only parity with
Rust); (b) the status-conversion fixture carries an independent wantSuccess
boolean and the Go/.NET fixture tests assert against it instead of recomputing
the formula under test.
Shared fixtures (authenticate-user.{echoed-credential,missing-payload,
return-value-only}.reply.json) + manifest + ClientBehaviorFixtures.md +
ClientLibrariesDesign.md updated in the same change. Tracking: CLI-40/41/44 -> Done.
This commit is contained in:
@@ -945,7 +945,7 @@ public sealed class MxGatewaySession : IAsyncDisposable
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
reply.EnsureProtocolSuccess().EnsureMxAccessSuccess();
|
||||
return reply.AddBufferedItem?.ItemHandle ?? reply.ReturnValue.Int32Value;
|
||||
return ResolveInt32Result(reply.AddBufferedItem?.ItemHandle, reply, "AddBufferedItem");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1141,7 +1141,14 @@ public sealed class MxGatewaySession : IAsyncDisposable
|
||||
verifierUserId,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
reply.EnsureProtocolSuccess().EnsureMxAccessSuccess();
|
||||
try
|
||||
{
|
||||
reply.EnsureProtocolSuccess().EnsureMxAccessSuccess();
|
||||
}
|
||||
catch (MxGatewayException ex)
|
||||
{
|
||||
throw MxGatewaySecretRedaction.Redacted(ex, ExtractSecretString(value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1215,7 +1222,14 @@ public sealed class MxGatewaySession : IAsyncDisposable
|
||||
verifierUserId,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
reply.EnsureProtocolSuccess().EnsureMxAccessSuccess();
|
||||
try
|
||||
{
|
||||
reply.EnsureProtocolSuccess().EnsureMxAccessSuccess();
|
||||
}
|
||||
catch (MxGatewayException ex)
|
||||
{
|
||||
throw MxGatewaySecretRedaction.Redacted(ex, ExtractSecretString(value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1285,8 +1299,15 @@ public sealed class MxGatewaySession : IAsyncDisposable
|
||||
verifyUserPassword,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
reply.EnsureProtocolSuccess().EnsureMxAccessSuccess();
|
||||
return reply.AuthenticateUser?.UserId ?? reply.ReturnValue.Int32Value;
|
||||
try
|
||||
{
|
||||
reply.EnsureProtocolSuccess().EnsureMxAccessSuccess();
|
||||
return ResolveInt32Result(reply.AuthenticateUser?.UserId, reply, "AuthenticateUser");
|
||||
}
|
||||
catch (MxGatewayException ex)
|
||||
{
|
||||
throw MxGatewaySecretRedaction.Redacted(ex, verifyUserPassword);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1337,7 +1358,7 @@ public sealed class MxGatewaySession : IAsyncDisposable
|
||||
MxCommandReply reply = await ArchestraUserToIdRawAsync(serverHandle, userIdGuid, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
reply.EnsureProtocolSuccess().EnsureMxAccessSuccess();
|
||||
return reply.ArchestraUserToId?.UserId ?? reply.ReturnValue.Int32Value;
|
||||
return ResolveInt32Result(reply.ArchestraUserToId?.UserId, reply, "ArchestrAUserToId");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1367,6 +1388,51 @@ public sealed class MxGatewaySession : IAsyncDisposable
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the int32 result of an OK command reply: the typed payload value when present,
|
||||
/// otherwise an int32 <c>return_value</c> when the reply carries one. A reply that provides
|
||||
/// neither is malformed and surfaces as <see cref="MxGatewayMalformedReplyException"/>
|
||||
/// rather than the historical <see cref="NullReferenceException"/>.
|
||||
/// </summary>
|
||||
/// <param name="typedValue">The typed payload value, or <see langword="null"/> when absent.</param>
|
||||
/// <param name="reply">The OK command reply.</param>
|
||||
/// <param name="operation">The MXAccess operation name, for the diagnostic message.</param>
|
||||
/// <returns>The resolved int32 result.</returns>
|
||||
private static int ResolveInt32Result(int? typedValue, MxCommandReply reply, string operation)
|
||||
{
|
||||
if (typedValue.HasValue)
|
||||
{
|
||||
return typedValue.Value;
|
||||
}
|
||||
|
||||
if (reply.ReturnValue is not null
|
||||
&& reply.ReturnValue.KindCase == MxValue.KindOneofCase.Int32Value)
|
||||
{
|
||||
return reply.ReturnValue.Int32Value;
|
||||
}
|
||||
|
||||
throw new MxGatewayMalformedReplyException(
|
||||
$"{operation} returned a malformed reply: OK reply carried neither the typed payload nor an int32 return_value",
|
||||
reply.SessionId,
|
||||
reply.CorrelationId,
|
||||
reply.ProtocolStatus,
|
||||
reply.HasHresult ? reply.Hresult : null,
|
||||
reply.Statuses.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the raw string form of a credential-bearing <see cref="MxValue"/> for redaction,
|
||||
/// or <see langword="null"/> when the value does not carry a string.
|
||||
/// </summary>
|
||||
/// <param name="value">The value written by a secured write.</param>
|
||||
/// <returns>The string payload, or <see langword="null"/>.</returns>
|
||||
private static string? ExtractSecretString(MxValue value)
|
||||
{
|
||||
return value.KindCase == MxValue.KindOneofCase.StringValue
|
||||
? value.StringValue
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes an MXAccess command on this session.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user