Files
Libation/Source/LibationFileManager/MaskedLogEntryPolicy.cs
T
Cursor Agentandrmcrackan 742e58d2e8 Mask the account on the paths that do not go through an exception
Account.ToString() returned "id - locale", so interpolating an account or
logging a non-destructured {Account} published the address. It now returns
the masked entry, with a DebuggerDisplay keeping the real values visible
while debugging. Nothing in the UI relied on it: both scan dialogs build
their own labels.

For structured logging, an ILogMasked type is reduced to its masked entry
by a destructuring policy, which covers the {@DebugInfo} shape most of
Libation's logging uses. And DecryptKey - the activation bytes - is now a
SecretString, so it has no plaintext for a reflective dump to find at all.
Its JSON stays the bare string it always was, so existing settings files
load unchanged.

A registered policy that nobody notices is missing protects nothing, so
the tests write through a logger built by ConfigureLogging itself rather
than a hand-made one. Deleting either registration fails them: the masked
object comes out whole, and a destructured secret renders as
{"HasValue":true} instead of its length.

The contribute guide now states the rule, since the reason for all of
this is invisible from the code alone: log files get attached to public
issues, so treat what goes in them as published.

Co-authored-by: rmcrackan <rmcrackan@gmail.com>
2026-08-17 02:04:09 +00:00

38 lines
1.2 KiB
C#

using Serilog.Core;
using Serilog.Events;
namespace LibationFileManager;
/// <summary>
/// Implemented by types that identify something private, and so must appear in a log only in masked form.
/// </summary>
public interface ILogMasked
{
/// <summary>The only form of this object that may reach a log file.</summary>
string MaskedLogEntry { get; }
}
/// <summary>
/// Reduces any <see cref="ILogMasked"/> to its masked entry when Serilog destructures it, so that a structured
/// log call - <c>{@Account}</c>, or an anonymous <c>{@DebugInfo}</c> object holding one - cannot publish the
/// unmasked object by accident.
/// <para>
/// This covers Serilog's own destructuring only. Serilog.Exceptions flattens a logged exception's properties
/// itself before Serilog sees them, so an exception must not carry one of these in the first place.
/// </para>
/// </summary>
public class MaskedLogEntryPolicy : IDestructuringPolicy
{
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
{
if (value is ILogMasked masked)
{
result = new ScalarValue(masked.MaskedLogEntry);
return true;
}
result = null!;
return false;
}
}