The service that hands out deposit addresses should be incapable of spending from them.
Not "trusted not to". Incapable.
That is the whole reason to derive deposit addresses from an account level extended public key. You hand the address service an xpub and an index. It derives the next address and gives it to a user. It holds no private key, so there is nothing on that box for an attacker to steal and move funds with. Signing sits somewhere else entirely, on a different trust boundary, usually touched by a different process and different people.
I have built custodial wallets for BTC and ETH, and this is the design decision that ages best. Most other key handling policy I have seen is a promise you keep by discipline. This one is kept by arithmetic: public derivation cannot produce the private half.
What surprises people is where the risk goes once you do this.
It goes into bookkeeping. Two things now decide whether a deposit is ever seen: the derivation path and the gap limit.
The derivation path is your account structure. Get it wrong once, in a migration or a rewrite, and you end up watching a perfectly valid branch of a tree that no user was ever given an address from. Funds land at addresses nobody is looking at. The chain is fine. Your ledger says nothing.
The gap limit is worse, because it fails quietly and only under real usage. You scan a window of unused addresses ahead of the last one you saw money on. Hand out addresses faster than deposits arrive (bots, abandoned checkouts, users who generate and never fund) and the funded address drifts past the end of that window. The deposit is confirmed on chain and invisible to you. Nobody gets an error. Support just gets a message saying "I sent it, where is it".
So I treat the watch set as a first class piece of state, not a derived convenience: every address handed out is recorded at issue time with its full path, and the watcher runs off that record rather than an optimistic scan window. The gap limit becomes a fallback for recovery, not the source of truth.
This came out of chain-addresses, a small open source project of mine for deriving and tracking deposit addresses. The longer write up on derivation paths and gap limit failure modes is on my blog: https://polycratia.com/c/d5daf33e
If you run custodial deposits in production: do you drive your watcher from issued addresses, or from a scan window over the xpub?