fix: forbid cross-user balance deduction in billing deduct route - #1377
FreshTVMax wants to merge 3 commits 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: |
Adds the missing route enforcement: a caller may only deduct from their own balance unless they are an authenticated admin or a service principal holding the billing:deduct scope. Replaces the nonexistent jsonsonnebb/jsonswebtoken imports with jsonwebtoken, fixes the invalid Record<unknown> casts, adds extractScopes, and restores the corrupted test files while adding runnable coverage for the 403/own/admin/service paths.
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