mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-09-12 21:57:19 -04:00
This is the reported leak. AuthenticationRequiredException held the live
Account, and Serilog.Exceptions writes every public property of a logged
exception into the log file - following nested objects as it goes - so
pausing auto-scan wrote the reporter's real address into a file we ask
people to attach to public issues. Their DecryptKey happened to be empty;
with activation bytes set it would have published those too.
The exception now carries an AccountSummary: masked entry and a
credentials flag, both safe to log, plus the owner-facing label behind a
method rather than a property, because reflection reads properties and
never calls methods. The constructor still takes an Account, so callers
and tests are unchanged.
The thrown message named the account too, and it reaches the log twice -
once as {Exception}, once as ExceptionDetail.Message - so it is masked
now. The GUI dialog still shows the full name and address, since that is
the owner's own screen. For the CLI, stderr is not teed into Serilog, so
that is where a headless user is told which account in full.
Two tests, one for the bug and one for the class of bug: the first logs a
real exception through the same WithExceptionDetails enricher Libation
configures and asserts no address, activation bytes, tokens, or cookies
come out. The second walks the public property graph of every exception
type in these assemblies and fails if one can reach an Account or an
Identity. Restoring the old property makes all of it fail, naming
"jade@example.com" and the path AuthenticationRequiredException.Account.
Co-authored-by: rmcrackan <rmcrackan@gmail.com>
44 lines
1.7 KiB
C#
44 lines
1.7 KiB
C#
namespace AudibleUtilities;
|
|
|
|
/// <summary>
|
|
/// What a failure needs to say about an account, captured so a live <see cref="Account"/> - with its tokens and
|
|
/// its activation bytes - never rides along on an exception into a log file.
|
|
/// <para>
|
|
/// Logs get attached to public issue reports, and Serilog.Exceptions writes every public property of a logged
|
|
/// exception into one, following nested objects as it goes. So everything public here is masked, and the label
|
|
/// meant for the account's owner is reachable only through a method: reflection reads properties and never calls
|
|
/// methods.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class AccountSummary
|
|
{
|
|
/// <summary>Safe to log. Also what <see cref="ToString"/> returns, so interpolating this cannot leak.</summary>
|
|
public string MaskedLogEntry { get; }
|
|
|
|
/// <summary>
|
|
/// True when the account was never fully logged in or its credentials were cleared, rather than merely
|
|
/// holding an expired session.
|
|
/// </summary>
|
|
public bool LooksLikeMissingCredentials { get; }
|
|
|
|
private readonly string ownerFacingLabel;
|
|
|
|
private AccountSummary(Account account)
|
|
{
|
|
MaskedLogEntry = account.MaskedLogEntry;
|
|
LooksLikeMissingCredentials = AccountCredentialStatus.LooksLikeMissingCredentials(account);
|
|
ownerFacingLabel = AccountCredentialStatus.FormatAccountLabel(account);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The name and address to show the person who owns the account, on their own screen. Never log this: use
|
|
/// <see cref="MaskedLogEntry"/>.
|
|
/// </summary>
|
|
public string RevealOwnerFacingLabel() => ownerFacingLabel;
|
|
|
|
public override string ToString() => MaskedLogEntry;
|
|
|
|
public static AccountSummary? From(Account? account)
|
|
=> account is null ? null : new AccountSummary(account);
|
|
}
|