bug: reject non-positive amounts in transfer/deposit/withdraw - #300
devin-ai-integration[bot] wants to merge 1 commit into
Conversation
Fixes a security bug where a negative transfer amount passed the insufficient-funds check and inverted the transfer direction, letting an authenticated user debit any other account by username.
Runtime verification — negative-amount transfer theft is fixedTested end-to-end through the UI with two live builds running side by side against separate MySQL schemas: BEFORE = upstream The attack:
|
| BEFORE (upstream) | AFTER (this PR) | |
|---|---|---|
| Result | accepted silently | rejected: "Amount must be greater than zero" |
| Attacker balance | $100 → $1100 | $100 → $100 |
| Victim balance | $5000 → $4000 | $5000 → $5000 |
Before/after screen recording:
BEFORE — attacker credited $1000, victim robbed of $1000:
AFTER — identical attack rejected, balances unchanged:
The victim ending at exactly $5040.00 ( = 5000 + the legitimate 40 transfer) independently confirms the -1000 transfer never moved any of their money.
Also verified on the fixed build
Negative deposit (-500) → error rendered on the dashboard, not a 500 error page (the BankController.deposit try/catch), balance unchanged at $100:
Negative withdraw (-500) → same error, balance unchanged at $100:
Regression: legitimate transfer of 40 → attacker $100 → $60, victim $5000 → $5040:
Not exercised at runtime: the scale > 2, > 1,000,000.00, and self-transfer branches of validateAmount (covered by the new unit tests only).








Summary
AccountService.transferAmountonly checkedfromAccount.balance >= amount, which a negativeamounttrivially satisfies. Both balance updates then invert —subtract(-X)credits the sender andadd(-X)debits the recipient — so any authenticated user could drain an arbitrary account just by knowing its username (POST /transfer?toUsername=victim&amount=-1000000).depositwas equally unguarded (negative deposit = self-withdrawal bypassing the funds check).Fix centralizes the guard in the service so every entry point is covered:
transferAmountalso rejects transfers to self (after recipient lookup), andBankController.depositnow wraps the service call in the same try/catch the other endpoints use so a rejected amount renders the dashboard error instead of a 500.Verification: no Maven/plugin repository is reachable from this session (only github.com is on the network allowlist), so
./mvnw testcannot resolve plugins. The newAccountServiceAmountValidationTestcompiles, and its scenarios were executed against the compiled classes with Mockito via a throwaway launcher (negative/zero/null/over-max/sub-cent/self transfer rejected with balances untouched; positive transfer, deposit and withdraw still move money) — all passed. CI is authoritative.Written by Devin
Devin-Org: engineering