Bicrypto 6.4.9
30 June 2026
Core v6.4.9
Release Date: June 30, 2026 Tags: SECURITY, MONEY-SAFETY, WEBHOOKS, AUTH, IDOR, WALLET, IDEMPOTENCY, DATABASE, BUG-FIXES
Overview
Version 6.4.9 is a large security and money-safety release closing a platform-wide audit of the core and every extension. Fiat-gateway webhooks no longer trust the caller's payload, authentication closes account-takeover and replay paths, owner-scoped endpoints stop leaking and tampering across accounts, and the wallet ledger is made idempotent and race-safe at the database level. It also adds an immutable wallet audit log and reconciliation jobs that recover stuck withdrawals.
This one takes more than pnpm updator. There are one-time migration commands to run either side of it, and the extension releases below are part of the same release — take them in the same window.
Update Instructions
Back up your database first. Then, for this release only, run these commands once (each migration command is a dry-run preview; add :apply to execute, and every step is idempotent so re-running is safe):
# 1) BEFORE updating — de-dup + convert the UTXO status column
pnpm db:migrate:6.4.9:before # preview what will change
pnpm db:migrate:6.4.9:before:apply # apply it
# 2) Run the normal update (unchanged)
pnpm updator
# 3) AFTER updating — drop old indexes, re-point FKs, backfills
pnpm db:migrate:6.4.9:after # preview what will change
pnpm db:migrate:6.4.9:after:apply # apply itThe :before step must run before pnpm updator (so the schema-sync doesn't trip over duplicate rows or mis-cast the UTXO status); the :after step runs once the new columns/indexes exist. Future updates just use pnpm updator as always — these 6.4.9 migration commands are one-time. The migration is run from the core and covers the extension schema changes as well.
Update the extensions in the same window. Each carries its own half of this audit, documented in its own patch notes: Ecosystem v6.2.6, NFT Marketplace v6.1.3, Copy Trading v6.1.2, Futures Trading v6.0.9, Forex Investment v6.1.1, Staking v6.1.1, ICO/STO/IDO v6.1.7, E-commerce v6.1.0, MLM & Affiliate v6.1.0, AI Market Maker v6.0.9, and AI Investments v6.1.0.
Added
Reconciliation, audit, and recovery
- Immutable wallet audit log — wallet operations now persist to an append-only audit log (best-effort, never blocking the operation, and deliberately kept clear of the wallet's own row lock) for compliance and forensics independent of the mutable transaction record.
- Spot-withdrawal reconciliation — a job recovers withdrawals stuck in
PROCESSING, converging to the exchange's true state or refunding (idempotently) when no payout can be confirmed. - Extension reconciliation jobs — futures-order, copy-trading, and MLM-condition reconciliation ship with their respective extensions (see Futures v6.0.9, Copy Trading v6.1.2, and MLM & Affiliate v6.1.0).
Changed
Data layer and schema
This release migrates several core records. The changes are applied partly by the normal schema sync and partly by the migration commands above:
- Changed transactions to carry a real idempotency key with a uniqueness guarantee, replacing the scan that could let two concurrent requests through.
- Changed every money figure — wallet, transaction, currency, admin profit and the staking amount and percentage — off floating-point onto exact decimals.
- Changed the transaction, wallet, user and role relationships so a hard user delete can no longer cascade away the ledger.
- Changed constraints and indexes: one KYC application per user per level, one NFT sale per on-chain transaction, new indexes on P2P trades, the unconditional "one investment per plan" uniqueness replaced by a rule that only counts active investments, around 60 duplicate merchant-gateway indexes cleaned up, the legacy staking pool table retired, and deposit and withdraw method status made non-nullable.
Fixed
Payment gateway security
Fiat webhooks credit the stored transaction, never the payload
Several fiat-deposit webhooks treated their signature as optional and read the credited amount straight from the request body, which let an unauthenticated caller forge a "completed" notification for an arbitrary amount. Signature verification is now mandatory and fails closed, and the amount that is credited comes from the stored pending transaction, never from the incoming payload. The PayFast ITN handler now requires a configured passphrase and a valid signature, hard-fails on ITN re-validation failure, and credits the stored transaction; the 2Checkout verify path confirms the order server-side instead of trusting a client-supplied status; and the Authorize.Net signature verifier no longer returns "valid" when the signing key is unset.
Stripe deposit sessions are bound to the caller
The Stripe deposit verify endpoint credited the caller while taking the amount/currency from the session, with no check that the session belonged to that caller — a leaked session id could be claimed into another user's wallet. The session's own owner must now match the authenticated user before crediting.
dLocal reversals and Paystack signatures hardened
dLocal refund/chargeback paths now route through the wallet service (deducting the actually-credited net amount, with a ledger record) and return an error to dLocal when the target wallet is missing so the reversal is retried instead of silently lost. Paystack signature comparison is now constant-time. PayU/Paytm completion was aligned to credit through the wallet service with deterministic keys.
Authentication and account security
Removed an unauthenticated password-takeover endpoint
A legacy "chat login" endpoint accepted credentials in the query string and, for an existing account, overwrote the password and issued a session with no current-password check, 2FA, or proof of ownership — a trivial account takeover. It no longer touches an existing account's credentials.
Google sign-in enforces the token audience
The Google access-token path only warned on an audience mismatch and then logged in by email, and registration could silently fall back to an unauthenticated userinfo fetch. Both now reject a token that was not issued for this installation's own Google client (and require a verified email) before trusting the identity, closing an OAuth confused-deputy takeover.
Wallet sign-in (SIWE) is replay-proof
The Sign-In-With-Ethereum nonce was generated client-side and never stored, expired, or consumed, so a captured (message, signature) pair was replayable indefinitely. The server now issues a single-use nonce (stored in Redis with a short TTL), and the login handler parses and atomically consumes that nonce before verifying the signature; the address regex is anchored, and the client chainId used for on-chain (EIP-1271) verification is constrained to a supported-chain allow-list so it can't be pointed at an arbitrary RPC. Wallet linking on the profile page was updated to sign a real SIWE message as well.
Account deletion is bound to its own token
The "confirm deletion" endpoint chose the target account from a request email while only checking that the deletion token was validly signed — letting an attacker self-issue a token and delete a victim's account. Deletion is now bound to the token's own subject.
Second factor, lockout, and code hardening
Wallet and Google logins now pass through the same 2FA gate as password login. Login lockout is checked before the password is verified and reset only on genuine success. Email/phone verification codes use a cryptographic RNG with attempt limits, the user-side TOTP secret is encrypted at rest, password-reset enforces the password policy and returns neutral responses (no account enumeration), account deletion requires explicit confirmation, JWT verification pins the algorithm and refuses weak/default secrets, and the session endpoint no longer echoes the raw access token.
Authorization and data access (IDOR)
Owner-scoped reads and writes
Several user-facing read, update and delete endpoints loaded a record by its id with no ownership check. They are now scoped to the authenticated user (and return 404 on a non-owned row): the transaction detail endpoint (which also stopped exposing the related user's PII), investment cancellation (which now refunds the investment's owner — not the caller — only for active investments), support-ticket close and reply (a non-staff user can no longer inject messages into another user's ticket or self-assign as an agent), the user-analytics endpoint (a request from the browser can no longer ask for another account's figures), and the exchange watchlist delete.
Extension authorization fixes for the NFT and ecosystem add-ons — auction-settlement gating, deposit-address unlock, public-token field exposure, and admin/blockchain endpoints — are documented in the NFT Marketplace v6.1.3 and Ecosystem v6.2.6 patch notes.
Admin and system controls
Database restore and file operations are contained
The database-restore path used a normalize-only sanitiser, so ../ sequences escaped the backup directory and any readable file could be executed as multi-statement SQL — immediately after the database was dropped. Restore now enforces containment after resolving the path, rejects path separators and .., and requires a .sql name. The bulk and single media-delete handlers and the PWA screenshot delete were given the same containment, and the update-zip extractor now rejects zip-slip entries that resolve outside the install root.
Settings, roles, and imports can't be used to escalate
The settings writer accepted arbitrary keys (it could flip withdrawal approval, demo mode, or fee percentages); it now validates each key against an allowlist. The role-sync endpoint gained the same Super-Admin guard the other role mutators have, user status changes can no longer ban a Super Admin, and the CSV user-import path can no longer mint a Super-Admin or backdoor account from a role named in the file. The unauthenticated KYC-provider probe endpoints (which leaked key validity and burned third-party quota) now require authentication and a permission.
Less exposure from admin reads
License status no longer returns the full Envato purchase code (it is masked), the settings cache-reload endpoint returns counts instead of echoing the whole settings table, the exchange-provider read masks credentials embedded in a proxy address, the CSV export password-hash option was removed, exchange API-key reads return a masked key (and updates can no longer pin a known secret or reassign ownership), and several finance/CRM option endpoints that were reachable by any logged-in user now require a permission. Test-notification/test-email endpoints can no longer be used as an open relay.
Stored-content safety
Operator/author HTML for blog posts and CMS pages is now sanitised before it reaches public visitors, the trading-settings update is allow-listed (closing a prototype-pollution path through a recursive merge), the media-filter no longer compiles attacker-supplied regular expressions (a ReDoS), slider links are scheme-validated, and the transaction/log delete endpoints refuse to destroy completed financial rows. Withdrawal approval now atomically claims the row before the irreversible exchange send, so a crash/retry can't broadcast a second on-chain withdrawal.
Notification template safety
No server-side template injection
Notification templates were rendered with EJS against operator-editable, database-stored bodies, so anyone able to persist a template could embed a scriptlet and run code on the backend. Rendering now uses a logic-less {{var}} interpolator that HTML-escapes values and cannot execute embedded tags. SMTP now validates TLS certificates (it previously accepted forged certs), user-scoped mail ignores a caller-supplied override address, and the WebPush provider restricts requests to known push-service hosts.
Wallet and ledger integrity
Idempotency is now enforced by the database
Wallet operations deduplicated retries with a non-locking text scan, so two concurrent requests with the same idempotency key could both pass the check and both post — a double credit or debit. Every transaction now carries a real idempotency key with a uniqueness guarantee enforced by the database, every wallet operation writes it, and a rejected duplicate is treated as the duplicate signal. Idempotency keys across the platform were made deterministic and unique per operation — no clock readings, random bytes or amount-only keys — so legitimate repeats aren't blocked and accidental double-submits are caught.
Races and lost updates closed
Wallet-to-wallet transfers now lock the two wallet rows in a deterministic order to avoid deadlocks; the private-ledger update takes a row lock so concurrent updates can't lose each other; and master-wallet / per-chain balance changes are done as locked read-modify-write instead of a read-then-write that could clobber a concurrent change. Custodial HD deposit-address derivation allocates its index under a row lock, so two concurrent wallet creations can no longer derive the same deposit address for different users.
Safer withdrawals
Custom-fiat withdrawals now enforce per-method min/max and decimal precision (matching spot), withdrawal idempotency keys carry a per-request component so a legitimate repeat isn't rejected as a duplicate, and manual/approval withdrawals are recorded as PENDING (not prematurely COMPLETED) until settlement. The confusingly-named withdrawApproval setting is superseded by a clearer withdrawAutoApprove (with a fallback to the legacy value so existing deployments keep working), and that setting is now protected from casual edits.
Exchange, binary and spot trading
Binary options
A binary order could be paid twice when a cancel raced settlement — they used different idempotency-key namespaces and the cancel didn't re-read under a lock. Cancellation now takes the same lock as settlement and re-reads the order before refunding. A client could also ask for a zero cancellation penalty and close a losing position with no penalty; the server now always uses its own configured penalty. The advertised per-user concurrent/daily/cooldown throttles are now actually enforced, the cancel path awaits its result so refund failures surface, and the binary health/status endpoints no longer expose internal state unauthenticated.
Spot & charts
The chart endpoint validated the symbol only for presence before joining it into a filesystem path; it is now format-checked so ../ can't escape the cache directory.
Platform infrastructure
CSRF, rate limiting, and identity
CSRF protection previously ran only on /logout; it now applies to all mutating requests (with a small pre-auth allowlist), comparing the request token to the session-stored token. The rate limiter fails closed for sensitive money/auth endpoints (instead of disabling itself on a Redis hiccup), derives the client IP correctly behind a trusted reverse proxy, and applies a baseline limit to public and API-key routes; API-key route scoping is now default-deny. Public WebSocket routes no longer take identity from a client-supplied user id (a guest can't register as another user). Long-running cron jobs run their status-change and refund as one transaction, UTXO selection takes a row lock that skips rows another worker already holds, request bodies are capped (with a real 413), and the access-token JWT is no longer placed in the WebSocket URL.
Frontend
Secrets, tokens, and XSS
The WebSocket client no longer appends the access token to the connection URL (it relies on the httpOnly cookie). The admin route guard now fails closed for unlisted /admin paths (it previously granted access by default). Unsanitised stored HTML on blog, legal, and notification-template views is now routed through the shared sanitiser, and the wallet cache is cleared on logout to prevent cross-user bleed in a shared tab.
Extension fixes
The platform audit also covered every paid extension. Those changes ship in the extensions' own releases — see the patch notes for NFT Marketplace v6.1.3 (race-safe settlement, chain-verified purchases), Ecosystem v6.2.6 (UTXO double-spend, CLOB matching/cancel), Futures Trading v6.0.9, Copy Trading v6.1.2 (follower fund flow), Forex Investment v6.1.1, Staking v6.1.1, ICO/STO/IDO v6.1.7, E-commerce v6.1.0 (atomic multi-item checkout), MLM & Affiliate v6.1.0, AI Market Maker v6.0.9, and AI Investments v6.1.0. The unified investment-ROI formula spans the core, AI, and Forex processors.