Skip to content

fix: enforce borrower ownership in contestDefault (BOLA) - #1836

Open
flare-999 wants to merge 2 commits into
LabsCrypt:mainfrom
flare-999:fix/1802-contest-default-ownership
Open

flare-999 wants to merge 2 commits into
LabsCrypt:mainfrom
flare-999:fix/1802-contest-default-ownership

Conversation

@flare-999

Copy link
Copy Markdown

Overview

contestDefault in backend/src/controllers/loanController.ts only checked that a LoanDefaulted event existed for the loan; it never verified that the authenticated caller was that loan's borrower. Any authenticated user could therefore contest someone else's default, and when an admin resolved the dispute the system would attribute it to the attacker (a Broken Object Level Authorization / IDOR defect).

Related Issue

Closes #1802

Changes

  • [MODIFY] backend/src/controllers/loanController.ts

    • The defaulted-loan lookup now also selects the event's address (the loan's borrower) instead of only loan_id.
    • Added an explicit ownership assertion: when address !== req.user.publicKey the handler throws AppError.forbidden(...) — HTTP 403.
    • The route already applies requireLoanOwner; this controller-level check makes the handler safe wherever it is mounted (defence in depth).
  • [MODIFY] backend/src/__tests__/loanDispute.test.ts

    • Updated the happy-path mock so the LoanDefaulted lookup returns the caller's own address.
    • Added a regression test: a LoanDefaulted event owned by another borrower now yields HTTP 403.
  • [MODIFY] backend/src/__tests__/integration/loanDisputeFlow.test.ts

    • Both contest mocks now return the borrower's address from the defaulted lookup so the ownership assertion is satisfied.

Verification Results

Implemented via GitHub Contents/Git API (no local clone).
Acceptance criteria mapping:
contests the loan's borrower against req.user.publicKey before writing any dispute row
returns HTTP 403 Forbidden when the caller is not the loan's borrower
no dispute row / LoanDisputed event is written on the forbidden path
regression test added for the cross-borrower case
Acceptance Criteria Status
borrower = req.user.publicKey is asserted loanBorrower !== borrower guard added in contestDefault
Non-borrower receives 403 AppError.forbidden thrown before any INSERT
Ownership resolved from persisted state address read from the LoanDefaulted contract_events row

@flare-999

Copy link
Copy Markdown
Author

@LabsCrypt both failing checks are green now.

Before: backend, env-docs-check
After: both pass.

Root cause & fix
This one was a stale base rather than a code defect: the contestDefault ownership checks and their tests were correct, but the branch predated the current main. Merging main in resolved both the backend run and the env-docs-check guard (documented env vars match the code). No changes were made to the PR's own implementation.

Head a3856993ce → ea4f31ecba.

This branch has not been deployed

No deployments
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.

[Security][Backend] Missing borrower ownership check in contestDefault enables unauthorized loan dispute hijacking (BOLA)

1 participant