fix(secrets-ui): preserve metadata on rotate + audit unexpected mutation/reveal failures

This commit is contained in:
Joseph Doherty
2026-07-15 17:31:34 -04:00
parent 0ab276dac0
commit 294f53b49a
8 changed files with 306 additions and 37 deletions
@@ -57,12 +57,53 @@
{
_error = null;
_busy = true;
bool deleted = false;
try
{
string actor = await SecretActorResolver.ResolveAsync(Services, AuthState);
await Store.DeleteAsync(Name, actor, _cts.Token);
try
{
await Store.DeleteAsync(Name, actor, _cts.Token);
await Audit.WriteAsync(
new AuditEvent
{
EventId = Guid.NewGuid(),
OccurredAtUtc = DateTimeOffset.UtcNow,
Actor = actor,
Action = "secret.delete",
Outcome = AuditOutcome.Success,
Category = "Secrets",
Target = Name.Value,
},
_cts.Token);
deleted = true;
}
catch (Exception)
{
// An unexpected store fault must still be audited as a Failure and surfaced to the
// operator instead of tearing down the circuit.
_error = "Unable to delete the secret.";
await WriteFailureAuditAsync(actor);
}
}
finally
{
_busy = false;
}
if (deleted)
{
await OnDeleted.InvokeAsync();
}
}
private async Task WriteFailureAuditAsync(string actor)
{
try
{
await Audit.WriteAsync(
new AuditEvent
{
@@ -70,17 +111,16 @@
OccurredAtUtc = DateTimeOffset.UtcNow,
Actor = actor,
Action = "secret.delete",
Outcome = AuditOutcome.Success,
Outcome = AuditOutcome.Failure,
Category = "Secrets",
Target = Name.Value,
DetailsJson = "{\"reason\":\"error\"}",
},
_cts.Token);
await OnDeleted.InvokeAsync();
}
finally
catch
{
_busy = false;
// Best-effort audit of a failure; never let audit itself surface into the event pipeline.
}
}