Skip to content

Commit e99c055

Browse files
fix: reject malformed OpenNode hashed_order
1 parent 42ae225 commit e99c055

2 files changed

Lines changed: 31 additions & 3 deletions

File tree

src/controllers/callbacks/opennode-callback-controller.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,24 @@ export class OpenNodeCallbackController implements IController {
6464

6565
const expectedBuf = hmacSha256(openNodeApiKey, request.body.id)
6666
const actualHex = request.body.hashed_order
67+
const expectedHexLength = expectedBuf.length * 2
68+
69+
if (
70+
actualHex.length !== expectedHexLength
71+
|| !/^[0-9a-f]+$/i.test(actualHex)
72+
) {
73+
debug('invalid hashed_order format from %s to /callbacks/opennode', remoteAddress)
74+
response
75+
.status(400)
76+
.setHeader('content-type', 'text/plain; charset=utf8')
77+
.send('Bad Request')
78+
return
79+
}
80+
6781
const actualBuf = Buffer.from(actualHex, 'hex')
6882

6983
if (
70-
expectedBuf.length !== actualBuf.length
71-
|| !timingSafeEqual(expectedBuf, actualBuf)
84+
!timingSafeEqual(expectedBuf, actualBuf)
7285
) {
7386
debug('unauthorized request from %s to /callbacks/opennode: hashed_order mismatch', remoteAddress)
7487
response

test/unit/controllers/callbacks/opennode-callback-controller.spec.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ describe('OpenNodeCallbackController', () => {
130130
expect(updateInvoiceStatusStub).not.to.have.been.called
131131
})
132132

133-
it('rejects callbacks with mismatched hashed_order', async () => {
133+
it('returns bad request for malformed hashed_order', async () => {
134134
request.body = {
135135
hashed_order: 'invalid',
136136
id: 'invoice-id',
@@ -139,6 +139,21 @@ describe('OpenNodeCallbackController', () => {
139139

140140
await controller.handleRequest(request, response)
141141

142+
expect(statusStub).to.have.been.calledOnceWithExactly(400)
143+
expect(setHeaderStub).to.have.been.calledOnceWithExactly('content-type', 'text/plain; charset=utf8')
144+
expect(sendStub).to.have.been.calledOnceWithExactly('Bad Request')
145+
expect(updateInvoiceStatusStub).not.to.have.been.called
146+
})
147+
148+
it('rejects callbacks with mismatched hashed_order', async () => {
149+
request.body = {
150+
hashed_order: '0'.repeat(64),
151+
id: 'invoice-id',
152+
status: 'paid',
153+
}
154+
155+
await controller.handleRequest(request, response)
156+
142157
expect(statusStub).to.have.been.calledOnceWithExactly(403)
143158
expect(sendStub).to.have.been.calledOnceWithExactly('Forbidden')
144159
expect(updateInvoiceStatusStub).not.to.have.been.called

0 commit comments

Comments
 (0)