Agent-Hardening TODO

Findings from an agent-perspective audit of afpay. Each item lists the priority, the file/symbol to change, the concrete fix, and the rationale. Items are ordered by priority within each section.

The threat model assumed throughout: a possibly-confused agent calls the daemon over RPC/REST; the operator runs the daemon and configures the allowlist; the agent must not be able to (a) drain funds beyond configured limits, (b) point a wallet at an attacker-controlled endpoint, or (c) cause silent double-spends on retry.


Status: 13/15 of the original items landed; item #12’s retry_after_ms half landed in 83302ed (the closed-enum half is still deferred). After a 3-angle post-hoc audit on 2026-05-30 the deferral list shrank: #8 Input::Quote and #11 wallet-selection ambiguity were re-promoted to P1 because both have real reliability impact, not just UX. See those items for the concrete agent failure modes.

Post-hoc audit findings (2026-05-30): the original list missed several load-bearing items, and two landed changes need re-shaping rather than straight revert. New items #16–#23 capture the gaps; the “Scope creep” section below is now narrowed to S2 (SOL cluster) plus a meta note about doc structure (S4). The earlier “revert the RPC handshake” recommendation was withdrawn — the handshake’s per-session salt actually buys forward secrecy against PSK leak (an in-scope threat) and the old 8192-entry FIFO replay cache had a real load-cycling bug that the new TTL cache fixes. The actual gap is unrelated (item #17). Backward compatibility is not a constraint when rolling things back — there are no pinned consumers.


Scope creep — to roll back or downgrade

S1. Revert per-session RPC handshakewithdrawn

S2. SOL cluster pinning via RPC hostname heuristic

S3 (watch-list, no action yet). Duplicated schema work

S4 (meta). Restructure doc for agent-author audience

P0 — Security correctness

1. Tighten URL allowlist matching (no host-prefix bypass)

2. Add idempotency_key to Send / CashuSend ✅ done

3. Enforce URL allowlist on WalletConfigSet

16. Extend allowlist enforcement to BTC core/electrum and LN endpoints ✅ done

17. Rate-limit the RPC Handshake call (session-table flood) ⚠️ partial


P1 — Defensive hardening

4. EVM chain_id check on send; SOL cluster tagging ⚠️ partial (SOL pending S2)

5. --public-listen flips allowlist to fail-closed + banner

6. Reservation TTL per-network, plus reconcile API ✅ done

7. Unify schema discovery across all modes

8. Input::Quote for pre-send fee estimation 🔺 promoted P2 → P1

18. Close the idempotency crash window between broadcast and finalize


P2 — Convenience and observability

9. Pipe mode: in-flight cap and explicit cancel

11. Disambiguate wallet auto-selection 🔺 promoted P2 → P1

12. error_code becomes a closed enum ⚠️ partial


P3 — Smaller correctness items

13. EVM u256 → u64 becomes checked

14. Audit container entrypoint for secret generation

15. Pipe parse-error verbosity


P2 — New items from 2026-05-30 audit (agent-author UX gaps)

19. Lazy-expire reservations on read paths

20. Input::ListReservations for agent recovery

21. Structured fields on Output::*.trace

22. schema_version on Output::Schema ⚠️ partial

23. Document partial-failure semantics on Output::Sent


Summary by priority

Legend: ✅ done · ⚠️ partial · ❌ open · 🔺 promoted on 2026-05-30 audit

P#ItemStatusTouches
P01URL allowlist exact origin matchtypes/config.rs
P02idempotency_key for sendsprotocol.rs, store, handler/pay
P03Allowlist on WalletConfigSethandler/wallet.rs
P016Allowlist BTC core/electrum + LN endpointhandler/wallet.rs, types/config.rs
P017Rate-limit RPC Handshake (session-table flood)⚠️mode/rpc/mod.rs (sep quota + cap open)
P14EVM chain-id check; SOL cluster tag⚠️EVM done; SOL via S2; handler/pay
P15--public-listen ⇒ fail-closed + bannertypes/config, mode startup
P16Per-network reservation TTL + reconcile APIspend/mod, handler/spend_guard
P17Input::Schema in all modesprotocol.rs, all modes
P18Input::Quote for fee estimation❌🔺protocol.rs, all providers
P111Multi-wallet selection ambiguity output❌🔺handler/pay, provider/cashu
P118Close idempotency crash window (Pending tombstone)spend/mod.rs, handler/pay.rs
P29Pipe in-flight cap + Input::Cancel⚠️mode/pipe.rs (cap done; Cancel open)
P210History ↔ reservation cross-linktypes/domain, spend/mod
P212Closed ErrorCode enum + retry_after_ms⚠️protocol.rs, all emit_error
P219Lazy-expire reservations on read pathsspend/mod.rs, handler/spend_guard
P220Input::ListReservations for agent recoveryprotocol.rs, spend/mod
P221Structured trace fields on every Output::*types/protocol.rs
P222schema_version on Output::Schema⚠️handler/schema.rs (git_sha open)
P223Document partial-failure semantics on Output::Sentdocs + handler/pay.rs
P313EVM u256 → u64 checkedprovider/evm.rs
P314Container secret-generation auditcontainer/docker/
P315Pipe parse-error scrubbing under public-listenmode/pipe.rs

Scope-creep section (above): S1 withdrawn · S2 still open · S3 logged · S4 (doc restructure) open.