Manav.id
Crypto · 4 min read

The custody API is the perimeter, and it authenticates a server

The custody API is the perimeter, and it authenticates a server

Policy engines in custody platforms are genuinely good: thresholds, allow-lists, quorum requirements, velocity limits. Underneath all of it, the API call that initiates a withdrawal is authenticated by a key held on infrastructure.

What can an attacker do with a stolen custody API credential?

Everything the policy permits. Platforms such as Fireblocks and BitGo bound withdrawals with allow-lists, thresholds, velocity limits and quorum rules. A credential on a compromised server operates inside all of them — repeatedly, under the threshold, to any address already on the list.

Key takeaways
  • Policy engines constrain what a compromised API credential can do; they do not prevent it being used within policy.
  • Automated withdrawal flows have no human at the moment of the transfer, which is the point of them and the exposure.
  • Requiring a human signature over the rendered withdrawal for a defined tier keeps automation for routine flows and closes the top of the range.

What the policy engine does

Policy engine configuredallow-list, thresholds, velocityAPI credential on a serverthe perimeterServer compromisedno policy violatedSub-threshold withdrawals, repeatedlyto an allow-listed addressVelocity cap reachedthe real limit
The policy engine works exactly as designed. The attacker simply operates within it.

Institutional platforms let an organisation express rules about what can move, to where, by whom, and how fast.

ControlEffect
Destination allow-listWithdrawals only to pre-approved addresses
Threshold tiersLarger amounts require more approvers
Velocity limitsCaps per period regardless of individual transaction size
Quorum requirementsMultiple approvers for defined categories
Time-of-day restrictionsTransactions outside a window are blocked or delayed

These are well designed and genuinely reduce exposure. An attacker with the API credential is bounded by them, which is the point.

What remains inside the boundary

Everything the policy permits. An attacker who has compromised the server holding the API credential can do whatever an automated flow is allowed to do.

The second point is where the loss accumulates. An attacker patient enough to stay under the threshold extracts value continuously without triggering anything.

The allow-list question

Worth examining separately because it is the control most organisations rely on most heavily.

An allow-list constrains destinations. Its strength therefore depends entirely on how addresses get onto it and how often it is pruned. In practice: addresses accumulate, the process for adding is lighter than the process for withdrawing, and nobody removes stale entries.

This suggests the highest-value gate is not the withdrawal at all — it is the allow-list addition, which is a rarer event with larger consequences.

Tiering with a human signature

# Withdrawal request arrives at the custody API wrapper

tier = classify(request)     # amount, destination age, asset

if tier == ROUTINE:
    # Below threshold, established destination, normal velocity
    forward_to_custody_api(request)

else:
    # Above threshold, new destination, or unusual pattern
    statement = render(request)        # full destination, amount,
                                       # asset, destination age,
                                       # cumulative period total
    receipt = await_human_signature(statement)
    verify(receipt) or refuse()
    forward_to_custody_api(request, receipt=receipt)

The rendered statement should include the cumulative total for the period. An individual withdrawal below the threshold looks routine; the fourteenth one that day does not, and only the cumulative figure reveals it.

Where the gate sits

An important design choice. A wrapper in front of the custody API can be bypassed by anything that can call the API directly.

PlacementRobustness
Wrapper service in your infrastructureBypassed if the attacker calls the custody API directly
Enforced by custody platform policyRobust — requires the platform to support receipt verification
BothWrapper for rendering and workflow; platform policy as the enforcement

The third row is the realistic architecture today, and it depends on the platform's policy engine supporting an external approval requirement. Where it does not, a wrapper plus tight network and credential restrictions is the available approximation.

What to ask a custody provider

  1. Can a withdrawal require an approval your platform cannot itself generate — an externally verified signature?
  2. What exactly does your API credential authenticate, and what would an attacker holding it be able to do within our policy?
  3. Can allow-list additions be gated separately and more strictly than withdrawals?
  4. Do your policy decisions produce evidence we can verify without calling you?
  5. What is the maximum value extractable in twenty-four hours under our current policy, without any human approval?

The fifth question is the one to ask first. Most organisations have never computed it, and the number is usually larger than the treasurer expects.

The allow-list is the weak point, not the threshold

An allow-list constrains destinations, so its strength depends entirely on how addresses get onto it and how often it is pruned. In practice addresses accumulate, the process for adding is lighter than the process for withdrawing, and nobody removes stale entries. That suggests the highest-value gate is not the withdrawal at all — it is the allow-list addition, a rarer event with larger consequences.

Where the gate should sit
PlacementRobustness
Wrapper in your own infrastructureBypassed by anything that can call the custody API directly
Enforced in the platform's policy engineRobust — requires the platform to support external approval
BothRealistic today: wrapper for rendering and workflow, platform policy for enforcement

Objections and honest limits

“Our velocity limits cap the loss.” They cap it at the velocity limit, which is the number worth computing. Ask what the maximum extractable value is in twenty-four hours under current policy with no human approval. Most treasurers have never computed it, and it is larger than expected.

“We require quorum above a threshold.” Then the attack runs below the threshold. Render the cumulative period total in the approval, not just the individual amount — the fourteenth sub-threshold withdrawal that day is the signal, and only the cumulative figure shows it.

Five questions for your custody provider

  1. Can a withdrawal require an approval your platform cannot itself generate? An externally verified signature, not an internal workflow state.
  2. What exactly does the API credential authenticate? And what could a holder do inside our current policy?
  3. Can allow-list additions be gated more strictly than withdrawals? The addition is the higher-consequence event.
  4. Do policy decisions produce evidence we can verify without calling you? Otherwise the record ends when the relationship does.
  5. What is the maximum extractable in 24 hours with no human approval? Compute it. This number decides how much of the above matters.

Terms used here

Policy engine
Rules constraining what a custody credential may do: destination allow-lists, value thresholds, velocity caps, quorum requirements and time windows.
Allow-list
The set of approved withdrawal destinations. Effective in proportion to how carefully entries are added and how often they are pruned.
Velocity limit
A cap on value moved per period, independent of individual transaction size. Frequently the real bound on a compromise.

Frequently asked questions

Are custody policy engines inadequate? No. They meaningfully bound what a compromised credential can do. They do not prevent it being used within policy, which is where patient attackers operate.

Why is the allow-list the weak point? Addresses accumulate, the addition process is lighter than the withdrawal process, and stale entries are rarely pruned. The addition is the rarer, higher-consequence event.

Why show cumulative totals in the approval? An individual sub-threshold withdrawal looks routine. The fourteenth that day does not, and only the cumulative figure reveals the pattern.

Where should the gate be enforced? In the custody platform's policy engine where supported. A wrapper in your own infrastructure can be bypassed by direct API calls.

Why gate the allow-list rather than the withdrawal? Because addresses accumulate through a lighter process than withdrawals use, and a hostile entry converts every future policy check into a pass.

Why show cumulative totals at approval? A single sub-threshold withdrawal looks routine. The pattern only becomes visible in the period total.

Where this fits in Manav

Manav sits in front of the custody API as a tier gate: routine withdrawals pass, and anything above threshold, to a new destination, or with an unusual cumulative total requires a signature over the rendered transfer that the platform itself cannot produce.

See withdrawal gating →

Sources and further reading