fix(transport): zero a bundle session's content when it expires, not only on apply/abandon (review 2026-10-21 04/C3)

This commit is contained in:
Joseph Doherty
2026-10-22 09:18:37 -04:00
parent fad08f7ce7
commit 55cebf2397
3 changed files with 76 additions and 4 deletions
@@ -153,7 +153,10 @@ public sealed class BundleSessionStore : IBundleSessionStore
{
if (!_sessions.TryGetValue(sessionId, out var session)) return null;
if (session.ExpiresAt > _timeProvider.GetUtcNow()) return session;
_sessions.TryRemove(sessionId, out _);
if (_sessions.TryRemove(KeyValuePair.Create(sessionId, session)))
{
ZeroContent(session);
}
return null;
}
@@ -169,9 +172,9 @@ public sealed class BundleSessionStore : IBundleSessionStore
var now = _timeProvider.GetUtcNow();
foreach (var kv in _sessions)
{
if (kv.Value.ExpiresAt <= now)
if (kv.Value.ExpiresAt <= now && _sessions.TryRemove(kv))
{
_sessions.TryRemove(kv.Key, out _);
ZeroContent(kv.Value);
}
}
@@ -186,6 +189,24 @@ public sealed class BundleSessionStore : IBundleSessionStore
}
}
/// <summary>
/// Review 2026-10-21 04/C3: zeroes an expired session's content buffer as it is
/// dropped. <c>ApplyAsync</c>/<c>Abandon</c> already zero before removing (T-007);
/// without this, a session left to expire kept its (compressed) decrypted bundle
/// content — script bodies, configuration and the bundle's secret values — in
/// managed memory until the GC reclaimed the array. Only the instance this store
/// actually removed is zeroed, so a concurrent re-<see cref="Open"/> under the same
/// id is never touched.
/// </summary>
private static void ZeroContent(BundleSession session)
{
var buf = session.DecryptedContent;
if (buf is { Length: > 0 })
{
Array.Clear(buf, 0, buf.Length);
}
}
/// <inheritdoc />
public int GetUnlockFailureCount(string bundleContentHash)
{