duly / SECURITY.md · raw
# SecurityDuly is unaudited. Please report problems through a private GitHub security advisory on this repository.## What is protected, and how| Risk | Protection ||---|---|| Someone takes a merchant's balance | Through the contract's code, only the balance holder can move it. The code has no owner role, pause or upgrade method. See the upgrade key below. || The relayer is drained | It sponsors a fixed list of methods on one contract, each with fixed gas and deposit limits, because the relayer pays any deposit attached inside a delegate action. NEAR returns a failed call's deposit to the sender, so arguments are checked with the contract's own rules first. Decoding is strict: one function call, no trailing bytes. Signatures are checked before any network call, and the key must belong to the sender. Daily limits apply per account and per IP address. Its balance is kept small. || Withdrawals are frozen by exhausting the budget | Moving money draws on a separate daily budget that other calls cannot touch. || A tampered response redirects a payment | The browser gets quotes from 1Click directly, verifies 1Click's signature over the deposit address, and checks the recipient, token, amount, invoice reference and refund address. This protects against a bad network path or API response. It cannot protect against someone who can deploy this website, because they control the page doing the checking. || Invoice details leak | They are encrypted in the browser with a per-invoice key. The server stores ciphertext only, and the key stays in the link fragment, which browsers do not send. || An invoice is altered | The chain stores a hash of the ciphertext, and the pay page refuses to offer payment if they differ. || The site reads a contract it should not | The page compares the deployed code hash with the expected one before offering live payment. || Spreadsheet formula injection | CSV cells that begin with a formula character are neutralised. || Script injection from invoice text | All text is rendered as text nodes, with no `innerHTML`, under a strict Content-Security-Policy. || The agent goes wrong | Its key can call two methods. The contract enforces the merchant's discount cap, allows it only on overdue invoices, and fixes the single sweep destination. |## Known limits- The relayer needs a full-access key, because NEP-366 wrappers cannot be sent with a function-call key. A stolen relayer key risks its small balance, not merchant funds.- Deploy access is the most sensitive credential. Whoever can deploy this site can serve a page that reads a signed-in merchant's key and moves their balance, and can change what clients are shown.- **Upgrade key:** a key is kept on the contract account during the hackathon, so "no admin" is not claimed. Whoever holds it could deploy new code and take the USDC. This is why amounts are kept tiny. RPC `view_access_key_list` shows the keys on the account.- A payout that fails after leaving the contract is refunded to the merchant's account on NEAR Intents. The Money page shows it, but moving it needs custom tooling with the recovery key.- An invoice link proves which invoice it opens, not who sent it. Anyone can issue an invoice with any business name, so clients should check the issuer account.- A refund is limited per invoice, not per payment, so the merchant should check the destination each time.- The recovery key, or the passkey it stands for, controls the account. Losing both loses access.- Anyone holding a full pay link can read that invoice.- Amounts, dates and account ids are public on-chain.- A payout through 1Click depends on that service. The contract cannot verify where a deposit address finally pays out, so the browser checks the signed quote before the merchant signs.- Secrets are never stored in this repository.