Repository navigation
fix: forbid cross-user balance deduction in billing deduct route - #1377
FreshTVMax wants to merge 1 commit into
Conversation
|
@FreshTVMax Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
Thanks for the contribution! We reviewed this PR while merging the open queue and couldn't merge it yet. Here's what needs fixing:
Once these are fixed, push to this branch and we'll take another look. |
ee649e4 to
5c387a4
Compare
|
@greatest0fallt1me — thanks for the review. All three points are addressed:
Supporting changes: |
|
@greatest0fallt1me Thanks — all three points are addressed on the current head:
Verified locally on this branch head with Node 20:
Please take another look. |
5c387a4 to
3dc9110
Compare
Overview
This PR closes a direct theft vector in
POST /api/billing/deduct: the endpoint previously accepted an optionaldeveloperIdin the request body and forwarded it as theuserIdtoBillingService.deduct, letting any authenticated caller drain another user's Soroban balance. The fix enforces that the deduction target must match the authenticated caller unless the caller is an admin (viaadminAuth) or a service principal holding an explicit billing scope, and audit-logs every rejected cross-user attempt.Related Issue
Changes
🔒 Deduction Authorization
[MODIFY]
src/middleware/requireAuth.tsres.locals.authenticatedUser(id + role/scope metadata) so downstream handlers can compare the request body'sdeveloperIdagainst the caller identity.x-user-idfallback behavior for backward compatibility while ensuring the resolved identity is what the deduct route authorizes against.[MODIFY]
src/middleware/adminAuth.tsres.localsso the deduct route can distinguish privileged callers from ordinary users.🧪 Tests
[MODIFY]
src/routes/billing/deduct.test.tsdeveloperIdreturns 403 andBillingService.deduct/ Soroban is never invoked.developerIdstill deducts from the authenticated caller.developerIdstill succeeds.[MODIFY]
tests/integration/billing-http.test.tsdeveloperId), and the privileged admin/service path.[MODIFY]
src/__tests__/billingDeductMetrics.test.tsVerification Results
developerIdof another user returns 403 and does not call Sorobandeduct.ts; asserted in unit + integration testsdeveloperIdor matching the caller still worksadminAuthexposes scope; route permits privileged callerslogger.auditinvoked on rejection with actor + targetSecurity and Failure-Mode Handling
developerId.BillingService.deductis invoked, so no on-chain or balance mutation occurs.logger.auditwith both the authenticated actor and the requested target, supporting incident response.developerIdor pass their own id are unaffected; only cross-user deductions by unprivileged callers change behavior.Closes #1252