diff --git a/src/relay/protocol-handshake.test.ts b/src/relay/protocol-handshake.test.ts index 68ea0178ef7..4b4695877b7 100644 --- a/src/relay/protocol-handshake.test.ts +++ b/src/relay/protocol-handshake.test.ts @@ -100,6 +100,36 @@ describe('handshake framing', () => { } }) + // endpointCredential is the one optional field, and it is the most pre-auth thing on the frame. + // Its only reader compares it, so a non-string refuses today by inequality rather than by type — + // which is luck, not a guarantee. Prove it at the parser, where every reader shares it. + it('rejects a present endpointCredential that is not a string', () => { + for (const endpointCredential of [{ toString: 1 }, 7, null, ['secret'], true]) { + const payload = Buffer.from( + JSON.stringify({ type: 'orca-relay-handshake', version: '0.1.0', endpointCredential }) + ) + expect( + () => parseHandshakeMessage(payload), + `endpointCredential=${JSON.stringify(endpointCredential)}` + ).toThrow(/Handshake field endpointCredential is not a string/) + } + }) + + // Absent must stay absent: a bridge that legitimately presents no credential is the common case, + // and refusing it here would close every unauthenticated-endpoint connection in the fleet. + it('still accepts a handshake with no endpointCredential, and one with a string', () => { + const bare = Buffer.from(JSON.stringify({ type: 'orca-relay-handshake', version: '0.1.0' })) + expect(parseHandshakeMessage(bare)).toEqual({ type: 'orca-relay-handshake', version: '0.1.0' }) + const withCredential = Buffer.from( + JSON.stringify({ type: 'orca-relay-handshake', version: '0.1.0', endpointCredential: 'sec' }) + ) + expect(parseHandshakeMessage(withCredential)).toEqual({ + type: 'orca-relay-handshake', + version: '0.1.0', + endpointCredential: 'sec' + }) + }) + it('still accepts a credential-mismatch reply, which carries no fields', () => { const payload = Buffer.from( JSON.stringify({ type: 'orca-relay-handshake-credential-mismatch' }) diff --git a/src/relay/protocol.ts b/src/relay/protocol.ts index a74ee13483b..0b5eee2704c 100644 --- a/src/relay/protocol.ts +++ b/src/relay/protocol.ts @@ -60,6 +60,20 @@ const HANDSHAKE_STRING_FIELDS: Readonly +> = { + 'orca-relay-handshake': ['endpointCredential'], + 'orca-relay-handshake-ok': [], + 'orca-relay-handshake-mismatch': [], + 'orca-relay-handshake-credential-mismatch': [] +} + export function parseHandshakeMessage(payload: Buffer): HandshakeMessage { const parsed: unknown = JSON.parse(payload.toString('utf-8')) if (typeof parsed !== 'object' || parsed === null) { @@ -82,6 +96,11 @@ export function parseHandshakeMessage(payload: Buffer): HandshakeMessage { throw new Error(`Handshake field ${field} is not a string`) } } + for (const field of HANDSHAKE_OPTIONAL_STRING_FIELDS[t as HandshakeMessage['type']]) { + if (msg[field] !== undefined && typeof msg[field] !== 'string') { + throw new Error(`Handshake field ${field} is not a string`) + } + } return msg as unknown as HandshakeMessage }