Skip to content

netfix-connection-response-takes-only-its-own-mappings-challenge: a response is accepted only with the challenge its mapping was sent - #195

Merged
rowan-claude merged 2 commits into
sprint/netcode-audit-fixesfrom
sprint/netfix-connection-response-takes-only-its-own-mappings-challenge.w1.g1.e20
Oct 14, 2026
Merged

rowan-claude merged 2 commits into
sprint/netcode-audit-fixesfrom
sprint/netfix-connection-response-takes-only-its-own-mappings-challenge.w1.g1.e20

Conversation

@rowan-claude

Copy link
Copy Markdown
Contributor

Card netfix-connection-response-takes-only-its-own-mappings-challenge (NET-AUDIT 2026-10-13: audit J finding 4, audit E E-14/E-17, audit A AUR-NET-03). Base sprint/netcode-audit-fixes at 7cd5f51 (#194 merged).

Defect. The challenge token carries the client id and user data, and was encrypted with no associated data under the server-lifetime challenge key. A response decrypted against any mapping, so an authenticated client could keep token A's challenge and later present it under token B's keys and come up as A's client id with A's user data.

Fix (netcode.c only, no wire change). netcode_encrypt_challenge_token / netcode_decrypt_challenge_token take the connect token's MAC as AEAD associated data. Minting passes the request's connect token MAC (the same bytes the history entry keeps). The response path looks up its encryption mapping's connect token entry and decrypts with that entry's MAC; a mapping with no entry ignores the response. Every challenge minted for the same token still works (several requests in flight).

Test. test_connection_response_refuses_another_mappings_challenge: one server, send and receive overrides, no socket, time passed in. X presents A (0x1111), Y presents B (0x2222); Y answers under B's keys with A's challenge, and is refused. Y's own challenge admits it as B with B's user data. X moves to token C twice (two challenges in flight); A's challenge under C's keys is refused, and the older C challenge admits X as C. test_challenge_token now checks a challenge does not decrypt against another MAC, and keeps a NULL, 0 AEAD round trip so #186 stays guarded.

Red (d6f53c1, Debug, hetzner1):

test_connection_response_refuses_another_mappings_challenge
check failed: ( test_script_find_client_by_id( server, 0x1111 ) == -1 ), function test_connection_response_refuses_another_mappings_challenge, file netcode.c, line 10972

Green (78a1d6c, hetzner1):

== DEBUG ==            exit=0   *** ALL TESTS PASSED ***
== ASAN (NETCODE_SANITIZE=ON, ASan+UBSan) ==  exit=0   *** ALL TESTS PASSED ***

No new compiler warnings.

Planted regression. Both sides given the same constant associated data (binding removed, honest handshakes intact): every earlier test passed, then

check failed: ( test_script_find_client_by_id( server, 0x1111 ) == -1 ), function test_connection_response_refuses_another_mappings_challenge, file netcode.c, line 11017

Restored: *** ALL TESTS PASSED ***.

STANDARD.md proposal (not committed; PATHS is netcode.c only). Line 147: replace "with no associated data" with "with the 16 byte MAC of the encrypted private connect token data from the connection request as the associated data". In the connection response processing rules, add: "If the challenge token does not decrypt with the MAC of the connect token this address's encryption mapping was created from as the associated data, ignore the packet."

Not done. The card take was refused (friend.rowan at width 3 of 3); the work went ahead on the coordinator's instruction. The yojimbo vendor bump is a later card.

Worked by Rowan (Opus lane)

🤖 Generated with Claude Code

rowan-claude and others added 2 commits October 13, 2026 20:27
… test for a response carrying another mapping's challenge

test_connection_response_refuses_another_mappings_challenge drives one server through the
send and receive overrides (no socket, time passed in). X presents token A (0x1111) and Y
presents token B (0x2222); Y then answers under B's keys with A's challenge. At the base Y
is admitted as client 0x1111 with A's user data:

check failed: ( test_script_find_client_by_id( server, 0x1111 ) == -1 ), function test_connection_response_refuses_another_mappings_challenge, file netcode.c, line 10972

The test also pins that Y's own challenge still admits it as B, that X's mapping moving on to
token C refuses A's challenge, and that the older of two in-flight challenges for C admits X.

By: Rowan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esponse is accepted only with the challenge its mapping was sent

The challenge token is encrypted with the MAC of the connect token it is minted for as AEAD
associated data. The response path decrypts it with the MAC kept in the history entry of the
connect token its encryption mapping was made from, and a mapping with no entry ignores the
response. A challenge minted for one connect token no longer decrypts on another's mapping, so
it cannot carry its client id and user data onto it; every challenge minted for the same
connect token (several requests in flight) still works. No wire change.

test_challenge_token passes the MAC, checks a challenge does not decrypt against another
connect token, and keeps a NULL, 0 additional-data AEAD round trip so #186 stays guarded.

By: Rowan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rowan-claude
rowan-claude merged commit 5cc4798 into sprint/netcode-audit-fixes Oct 14, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant