feat: security audit fixes

This commit is contained in:
ZOMBIIIIIII
2026-05-13 00:17:32 +03:00
parent e87d178d71
commit 1498ed3431
31 changed files with 2198 additions and 339 deletions

View File

@@ -0,0 +1,43 @@
/**
* JWT ↔ wallet-address binding.
*
* Защита от drain-вектора: проксируемые endpoint'ы (swap-build, relay-quote) принимают
* `userAddress`/`recipient` как body param. Если не привязать к JWT-юзеру —
* authenticated user A может set `userAddress=<user B's addr>` и:
* - swap output идёт к B (бесплатный slippage steal)
* - reentrancy: B's address — malicious contract, callback дрянит
* - bridge `recipient=attacker` → victim signs → funds to attacker
*
* Этот хелпер находит wallet user'а по chain и проверяет match.
*/
import { WalletModel } from '../models/wallet.model';
import type { ChainCode } from './address-validators';
/**
* Throws if address ≠ user's wallet для данного chain.
* Returns the canonical (DB-stored) address для использования вместо user input.
*
* Сравнение case-insensitive только для EVM (где checksum mixed-case = same address).
* Для BTC/TRX/SOL — strict equality (base58/bech32 case-sensitive).
*/
export async function assertUserOwnsAddress(
userId: string,
chain: ChainCode,
candidateAddress: string,
): Promise<string> {
const wallet = await WalletModel.findByUserAndChain(userId, chain);
if (!wallet) {
throw new Error(`No ${chain} wallet for user — POST /wallets/create first`);
}
const isEvm = chain === 'ETH' || chain === 'BSC';
const dbAddr = wallet.address;
const candidate = String(candidateAddress ?? '').trim();
const match = isEvm
? candidate.toLowerCase() === dbAddr.toLowerCase()
: candidate === dbAddr;
if (!match) {
throw new Error(`Address ${candidate} does not match user's ${chain} wallet ${dbAddr}`);
}
return dbAddr;
}