@@ -3053,6 +3053,7 @@ test("handleInbox() nonce consumption on valid signed request", async () => {
30533053} ) ;
30543054
30553055test ( "handleInbox() nonce replay prevention" , async ( ) => {
3056+ const [ meterProvider , recorder ] = createTestMeterProvider ( ) ;
30563057 const activity = new Create ( {
30573058 id : new URL ( "https://example.com/activities/nonce-3" ) ,
30583059 actor : new URL ( "https://example.com/person2" ) ,
@@ -3075,7 +3076,10 @@ test("handleInbox() nonce replay prevention", async () => {
30753076 rsaPublicKey3 . id ! ,
30763077 { spec : "rfc9421" , rfc9421 : { nonce } } ,
30773078 ) ;
3078- const federation = createFederation < void > ( { kv : new MemoryKvStore ( ) } ) ;
3079+ const federation = createFederation < void > ( {
3080+ kv : new MemoryKvStore ( ) ,
3081+ meterProvider,
3082+ } ) ;
30793083 const context = createRequestContext ( {
30803084 federation,
30813085 request : signedRequest ,
@@ -3107,6 +3111,7 @@ test("handleInbox() nonce replay prevention", async () => {
31073111 onNotFound : ( ) => new Response ( "Not found" , { status : 404 } ) ,
31083112 signatureTimeWindow : { minutes : 5 } ,
31093113 skipSignatureVerification : false ,
3114+ meterProvider,
31103115 inboxChallengePolicy : {
31113116 enabled : true ,
31123117 requestNonce : true ,
@@ -3132,6 +3137,19 @@ test("handleInbox() nonce replay prevention", async () => {
31323137 "no-store" ,
31333138 "Challenge response must have Cache-Control: no-store" ,
31343139 ) ;
3140+ const failures = recorder . getMeasurements (
3141+ "activitypub.signature.verification_failure" ,
3142+ ) ;
3143+ assertEquals ( failures . length , 1 ) ;
3144+ assertEquals ( failures [ 0 ] . value , 1 ) ;
3145+ assertEquals (
3146+ failures [ 0 ] . attributes [ "activitypub.remote.host" ] ,
3147+ "example.com" ,
3148+ ) ;
3149+ assertEquals (
3150+ failures [ 0 ] . attributes [ "activitypub.verification.failure_reason" ] ,
3151+ "invalidNonce" ,
3152+ ) ;
31353153} ) ;
31363154
31373155test (
@@ -3273,6 +3291,7 @@ test(
32733291test (
32743292 "handleInbox() actor/key mismatch does not consume nonce" ,
32753293 async ( ) => {
3294+ const [ meterProvider , recorder ] = createTestMeterProvider ( ) ;
32763295 // A request that has a valid RFC 9421 signature with a nonce, but the
32773296 // signing key does not belong to the claimed actor. The nonce must NOT be
32783297 // consumed so the legitimate sender can still use it.
@@ -3304,7 +3323,10 @@ test(
33043323 rsaPublicKey3 . id ! ,
33053324 { spec : "rfc9421" , rfc9421 : { nonce } } ,
33063325 ) ;
3307- const federation = createFederation < void > ( { kv : new MemoryKvStore ( ) } ) ;
3326+ const federation = createFederation < void > ( {
3327+ kv : new MemoryKvStore ( ) ,
3328+ meterProvider,
3329+ } ) ;
33083330 const context = createRequestContext ( {
33093331 federation,
33103332 request : maliciousRequest ,
@@ -3336,6 +3358,7 @@ test(
33363358 onNotFound : ( ) => new Response ( "Not found" , { status : 404 } ) ,
33373359 signatureTimeWindow : { minutes : 5 } ,
33383360 skipSignatureVerification : false ,
3361+ meterProvider,
33393362 inboxChallengePolicy : {
33403363 enabled : true ,
33413364 requestNonce : true ,
@@ -3357,6 +3380,19 @@ test(
33573380 true ,
33583381 "Nonce must not be consumed when actor/key ownership check fails" ,
33593382 ) ;
3383+ const failures = recorder . getMeasurements (
3384+ "activitypub.signature.verification_failure" ,
3385+ ) ;
3386+ assertEquals ( failures . length , 1 ) ;
3387+ assertEquals ( failures [ 0 ] . value , 1 ) ;
3388+ assertEquals (
3389+ failures [ 0 ] . attributes [ "activitypub.remote.host" ] ,
3390+ "example.com" ,
3391+ ) ;
3392+ assertEquals (
3393+ failures [ 0 ] . attributes [ "activitypub.verification.failure_reason" ] ,
3394+ "actorKeyMismatch" ,
3395+ ) ;
33603396 } ,
33613397) ;
33623398
0 commit comments