< Summary - Heimdall API

Information
Class: ArturRios.Heimdall.Shared.Security.LogSafeEmail
Assembly: ArturRios.Heimdall.Shared
File(s): /home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Shared/Security/LogSafeEmail.cs
Line coverage
100%
Covered lines: 4
Uncovered lines: 0
Coverable lines: 4
Total lines: 57
Line coverage: 100%
Branch coverage
100%
Covered branches: 2
Total branches: 2
Branch coverage: 100%
Method coverage
100%
Covered methods: 1
Fully covered methods: 1
Total methods: 1
Method coverage: 100%
Full method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Reference(...)100%22100%

File(s)

/home/runner/work/heimdall-api/heimdall-api/src/Application/ArturRios.Heimdall.Shared/Security/LogSafeEmail.cs

#LineLine coverage
 1using System.Security.Cryptography;
 2using System.Text;
 3
 4namespace ArturRios.Heimdall.Shared.Security;
 5
 6/// <summary>
 7///     Turns an email address into a stable reference that can be written to a log without writing
 8///     the address (NFR-22).
 9/// </summary>
 10/// <remarks>
 11///     <para>
 12///         <b>Why not the address.</b> A log line saying a password reset was sent to a named person
 13///         is personal data — the address identifies them, and the line says something about them.
 14///         The files are retained for a year and are read by operators, so the address would sit
 15///         there, in bulk, long after the operational question it answered was closed.
 16///     </para>
 17///     <para>
 18///         <b>Why not simply drop it.</b> Operators need to correlate: whether the three failures in
 19///         the log are one address retrying or three different people is the first question asked of
 20///         a delivery problem, and a line with no reference at all cannot answer it. A stable
 21///         reference keeps the correlation and drops the disclosure.
 22///     </para>
 23///     <para>
 24///         <b>What this is and is not.</b> It is the first twelve hex characters of the SHA-256 of
 25///         the normalised address. It is not reversible, and it defeats bulk harvesting: a reader of
 26///         the logs cannot recover the addresses. It does <em>not</em> defeat confirmation — somebody
 27///         who already suspects a particular address can hash it and look. That is an accepted
 28///         limit, and the honest way to describe this is pseudonymisation rather than anonymisation.
 29///         Anything stronger would need a keyed hash and a key to manage, which buys little against
 30///         a threat model where the log reader is an operator rather than an attacker.
 31///     </para>
 32/// </remarks>
 33public static class LogSafeEmail
 34{
 35    /// <summary>Hex characters kept. Twelve is 48 bits — ample against accidental collision.</summary>
 36    private const int ReferenceLength = 12;
 37
 38    /// <summary>
 39    ///     A stable, non-reversible reference to <paramref name="email" />, or <c>"(none)"</c> when
 40    ///     there is no address to refer to.
 41    /// </summary>
 42    /// <remarks>
 43    ///     Normalised to lower case and trimmed first, so the same address logged from two call
 44    ///     sites that disagree about casing still correlates.
 45    /// </remarks>
 46    public static string Reference(string? email)
 47    {
 15048        if (string.IsNullOrWhiteSpace(email))
 49        {
 350            return "(none)";
 51        }
 52
 14753        var digest = SHA256.HashData(Encoding.UTF8.GetBytes(email.Trim().ToLowerInvariant()));
 54
 14755        return Convert.ToHexStringLower(digest)[..ReferenceLength];
 56    }
 57}

Methods/Properties

Reference(System.String)