T09 · Insecure Skill Coding Practices
- Location
nano_invoice/core.py:36- Finding
Attacker-Controlled Mismatched Payment Permanently Closes an Invoice
- Content
View full analysis
Vulnerability Details
File Location:
nano_invoice/core.py, lines 36 and 240–241 and 478–481
Vulnerability Type: Untrusted ledger input causes an unauthorized invoice state transition
Risk Level: MediumComplete Code Snippet
python SETTLING_KINDS = ("payment", "underpaid", "overpaid") KIND_TO_STATE = {"payment": "paid", "underpaid": "underpaid", "overpaid": "overpaid"}python if kind in SETTLING_KINDS: _transition(c, invoice_id, KIND_TO_STATE[kind], now, observed)python if current.state == "open": kind = ("payment" if ob["amount_raw"] == inv.pay_raw else "underpaid" if ob["amount_raw"] < inv.pay_raw else "overpaid") try: observations.append(_record(store, ob, kind, inv.id, None, now, view)) continueTechnical Analysis
The Skill assigns each invoice an exact payment amount containing a unique low-order tag. Ledger payments ending in that tag are associated with the invoice.
A remote payer controls the amount of a payment sent to the merchant. When such a payment has the invoice’s tag but differs from the required
pay_raw, the code classifies it asunderpaidoroverpaid. Both classifications are included inSETTLING_KINDS, soStore.record()invokes_transition()and changes the invoice fromopento a terminalunderpaidoroverpaidstate.The database trigger and transition rules prevent terminal invoices from returning to
open. Consequently, a later payment of the correct amount is treated as a duplicate instead of settling the invoice.This also contradicts the documented behavior in
SKILL.mdlines 50–51, which states that a wrong payment amount does not close an invoice.Attack Path
- A merchant creates an invoice and gives its exact tagged payment amount to a buyer.
- The buyer learns the invoice tag from the disclosed amount.
- Before paying correctly, the buyer sends a confirmed payment to the merchant with an amount that ends in the ...[truncated 1116 chars]
- Remediation
View remediation
Remediation Suggestions
- Remove
underpaidandoverpaidfromSETTLING_KINDS; only an exactpaymentshould transition an invoice topaid. - Record mismatched payments as non-settling observations or refund candidates while leaving the invoice in the
openstate. - Separate payment-record classification from invoice lifecycle transitions so recording an anomalous transaction cannot implicitly close an invoice.
- Preserve the one-block/one-record protections, but permit a later exact payment to settle the still-open invoice.
- Add regression tests covering:
- Underpayment followed by exact payment.
- Overpayment followed by exact payment.
- Multiple mismatched payments before settlement.
- Mismatched payments after expiration.
- Concurrent mismatched and exact payments.
- Ensure implementation and documentation consistently define whether and when underpayments or overpayments may close an invoice.
- Remove
