fix(inbound): single-use nonce on relay provenance proofs (review 2026-10-21 05/O1)

This commit is contained in:
Joseph Doherty
2026-10-22 08:31:54 -04:00
parent 26baa5a170
commit e93af5a21f
3 changed files with 56 additions and 4 deletions
@@ -62,6 +62,29 @@ public class RelayProvenanceTests
if (tamper == "none") Assert.Equal(original.Items[AuditWriteMiddleware.InboundExecutionIdItemKey], row.ExecutionId);
}
/// <summary>Verifies that a captured relay proof cannot be replayed.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task O1_ReplayedProof_IsRejectedOnSecondUse()
{
// Review 2026-10-21 05/O1: the proof carries a nonce and the receiver remembers it.
var options = new InboundApiOptions { ApiKeyPepper = new string('p', 32) };
var original = Context("{}", "10.4.5.6");
original.Items[AuditWriteMiddleware.InboundExecutionIdItemKey] = Guid.NewGuid();
var header = RelayProvenance.Sign(original, "{}"u8.ToArray(), options.ApiKeyPepper)!;
var first = Context("{}", "10.0.0.2");
first.Request.Headers[RelayProvenance.Header] = header;
await RelayProvenance.VerifyAsync(first, options);
var replay = Context("{}", "10.0.0.2");
replay.Request.Headers[RelayProvenance.Header] = header;
await RelayProvenance.VerifyAsync(replay, options);
Assert.True(RelayProvenance.IsVerified(first));
Assert.False(RelayProvenance.IsVerified(replay));
Assert.Equal("10.0.0.2", RelayProvenance.CallerIp(replay));
}
/// <summary>Verifies that directly forged forwarding headers cannot change the audited caller.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]