Human Factor Auth for treasury
Require a password or passkey from a real person before a treasury wallet can send, swap, or export.
Human Factor Auth (HFA, v0.56) puts a person in the loop on the operations that move money. Treasury wallet send, swap, and export can each require a password or a passkey before they proceed: regardless of what token the caller holds.
The policy resolves in a fixed order, most specific first:
- The user's own human_factor_auth_policies.
- Then human_factor_auth on the relevant wallet_spend_policies.
- Then the system defaults.
HFA is about proving a human is present. It is deliberately separate from the agent guardrails in the Transactions track, which bound what an agent may attempt in the first place.
- 1
Read the current policy.
bashcurl -s https://api.1claw.co/v1/auth/human-factor-auth \ -H "Authorization: Bearer $ONECLAW_TOKEN" - 2
Require a passkey for sends and exports, and a password for swaps.
bashcurl -s -X PUT https://api.1claw.co/v1/auth/human-factor-auth \ -H "Authorization: Bearer $ONECLAW_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "send": { "method": "passkey" }, "swap": { "method": "password" }, "export": { "method": "passkey" } }' - 3
Clients can prefetch the effective policy so they know which step-up to prompt for before the user hits send.
bashcurl -s https://api.1claw.co/v1/treasury/wallets/auth-policy \ -H "Authorization: Bearer $ONECLAW_TOKEN" - 4
Satisfy the step-up. Mint a re-auth token, then present it on the protected call.
bashcurl -s -X POST https://api.1claw.co/v1/auth/reauth/begin \ -H "Authorization: Bearer $ONECLAW_TOKEN" \ -d '{"method":"passkey"}' # complete the ceremony, then send the resulting rat_ token: # -H "X-Auth-Confirm: rat_..."
Two webhooks make HFA observable: human_factor_auth.satisfied and human_factor_auth.denied. A run of denials is a strong signal that something is trying to move funds without a human.
Social-login users may have no password. If you require the password method they cannot satisfy it: have them enrol a passkey or TOTP at /settings/security first.
Where this goes wrong in practice. HFA is the control most sensitive to human factors, which is exactly why the failures are behavioural rather than technical.
- The threshold is set where prompts are constant. A step-up that fires on every operation is satisfied reflexively within a week, and reflexive satisfaction is indistinguishable from no control.
- A required factor cannot be presented. Social-login users have no password, so a password requirement is unsatisfiable rather than strict, and the failure looks like a bug rather than a policy choice.
- Clients do not prefetch the policy. Without reading the effective policy first, the interface discovers the requirement by failing the send, which trains users to retry rather than to authenticate.
- Denials are not monitored. human_factor_auth.denied is the signal that something is repeatedly attempting to move funds without a person, and it is worth an alert rather than a dashboard.
Sustained denials are more interesting than sustained approvals. Approval means the system is being used; denial means something is trying and failing, which is either a broken client or an attacker.
Decide
You set the HFA policy to require a password for treasury sends. A batch of users, all of whom signed up through Google, immediately cannot send at all.
What happened?
Check your understanding
3 questionsIn what order does an HFA policy resolve?
Why prefetch GET /v1/treasury/wallets/auth-policy?
A social-login user has no password set. What breaks if the HFA policy requires the password method?