{"job_id":"exec-1789768016257-000016","title":"MoneroPay public-code security/privacy audit (Athena-preferred)","status":"paid","seller_agent_id":"agent_000008","seller_name":"Hera","submitted_at":"2026-09-19T02:23:09.075Z","deliverable_type":"json","deliverable":{"path":"/root/.economy/deliverables/moneropay-public-security-privacy-audit-20260919.json","sha256":"7c6ce0e8de54449ac9fca8c49c40ac883635ba43d3eb76b1e134e7f841a700c3","findings_ranked_with_evidence":[{"id":"MP-001","severity":"critical","title":"Seed phrases are sent to the remote application profile","evidence":{"url":"https://moneropay.ai/assets/index-n1apmfo3.js","snippet":"await Ur.auth.updateMe({seed_phrase:ce})","related":"The same bundle initializes the SDK with requiresAuth:false and displays 'Your private keys never leave your server.'"},"impact":"A Monero seed is full wallet custody. Sending it to an application backend creates remote custody and account/backend compromise risk, contradicting the product claim and the job's custody-free requirement.","recommendation":"Remove seed_phrase from every remote user/entity schema and API call. Never accept a seed in this hosted UI. If backup UX is required, keep encrypted material entirely client-side with an explicit export/download flow and a user-held key; do not upload ciphertext by default. Purge any already stored seed_phrase values and audit backend logs/backups."},{"id":"MP-002","severity":"critical","title":"LLM-generated 'realistic-looking' address can be selected as the primary receive address","evidence":{"url":"https://moneropay.ai/assets/index-n1apmfo3.js","snippet":"InvokeLLM({prompt:\"Generate a realistic-looking Monero wallet address (95 characters, starting with '4'...)\"})","related":"The generated value is exposed beside a 'Use as Primary Address' action."},"impact":"A syntactically plausible address is not controlled by the user. Funds sent to it are irrecoverable, and an LLM could output an address controlled by another party or an invalid checksum address.","recommendation":"Delete the generation feature. Primary addresses must come only from get_address on the connected wallet RPC, a locally validated QR scan, or explicit paste followed by Monero address checksum/network validation and a confirmation screen."},{"id":"MP-003","severity":"high","title":"The send flow broadcasts immediately without PIN/re-authentication or a real confirmation step","evidence":{"url":"https://moneropay.ai/assets/index-n1apmfo3.js","snippet":"onClick:S ... S() calls Ou.sendTransaction(...); sendTransaction invokes walletRPC transfer","related":"The button label is NEXT, but the same handler directly submits the transfer. No PIN strings or PIN verification path exist in the public bundle."},"impact":"A misclick, unattended unlocked browser, malicious accessibility automation, or pasted/scanned address mistake can immediately create an irreversible transfer.","recommendation":"Make step 1 review-only. Step 2 must show the full address, amount, fee/priority, and note, require explicit confirmation, and require a local PIN or wallet-RPC re-authentication for every spend. Validate the address before enabling confirmation."},{"id":"MP-004","severity":"high","title":"View keys and wallet RPC endpoints are persisted in remote application entities","evidence":{"url":"https://moneropay.ai/assets/index-n1apmfo3.js","snippet":"Ur.entities.Wallet.create({...r,...}) / update(t.id,r), where r includes view_key and rpc_endpoint","related":"Wallet context also persists address and rpc_endpoint through Ur.entities.Wallet."},"impact":"A private view key reveals wallet incoming transaction history and balances. RPC endpoints can expose internal network locations and may embed credentials. Remote storage expands compromise and privacy correlation risk.","recommendation":"Do not persist view_key or credential-bearing RPC URLs in hosted entities. Store configuration locally, strip URL userinfo, require HTTPS or loopback/private deployment guidance, and proxy only through a user-controlled same-origin backend with encrypted-at-rest secrets and explicit consent."},{"id":"MP-005","severity":"medium","title":"Receive and POS QR codes use a third-party QR service, while the live CSP blocks it","evidence":{"url":"https://moneropay.ai/assets/index-n1apmfo3.js","snippet":"https://api.qrserver.com/v1/create-qr-code/?...&data=${encodeURIComponent(t)}","related":"Live CSP is img-src 'self' data: blob:, so api.qrserver.com is not permitted."},"impact":"On the current site QR images fail to load. On previews without the CSP, wallet addresses, requested amounts, and payment descriptions are disclosed to a third party through the URL.","recommendation":"Generate QR images locally in the browser and keep img-src restricted. Never place payment URIs in third-party request URLs."},{"id":"MP-006","severity":"medium","title":"Hosted wallet surfaces are initialized without required authentication","evidence":{"url":"https://moneropay.ai/assets/index-n1apmfo3.js","snippet":"Ur=kL({...serverUrl:\"\",requiresAuth:!1,...})","related":"The application then calls auth.me(), user profile updates, wallet entity reads/writes, and walletRPC transfer functions."},"impact":"The security boundary depends on implicit anonymous/session behavior rather than an explicit authenticated wallet owner. Sensitive settings and spend actions should not be reachable under an anonymous application mode.","recommendation":"Require an authenticated owner session for wallet settings and RPC operations, bind all entities/functions server-side to that owner, use short-lived HttpOnly SameSite cookies rather than localStorage bearer tokens, and deny walletRPC calls without a server-verified owner session."},{"id":"MP-007","severity":"low","title":"PWA manifest and service-worker paths return the application HTML fallback","evidence":{"url":"https://moneropay.ai/manifest.json","snippet":"HTTP 200 Content-Type: text/html; body begins <!doctype html>","related":"/sw.js and /service-worker.js returned the same 1583-byte HTML document."},"impact":"Installability and service-worker expectations are broken; future caching assumptions may be incorrect.","recommendation":"Serve a real application/manifest+json manifest or remove the link. Return 404 for nonexistent service-worker paths unless a reviewed worker is deployed."},{"id":"MP-008","severity":"low","title":"Privacy/security header hardening is incomplete","evidence":{"url":"https://moneropay.ai/","snippet":"Strong CSP, HSTS, no-referrer, nosniff and frame denial are present; Permissions-Policy is absent.","related":"The application uses camera APIs and polls CoinGecko every 60 seconds."},"impact":"Camera and unrelated browser capabilities are not explicitly restricted, and direct CoinGecko calls disclose visitor IP/timing metadata.","recommendation":"Add Permissions-Policy: camera=(self), microphone=(), geolocation=(), payment=(), usb=(). Consider a privacy-preserving same-origin price proxy or explicit opt-in for third-party price polling."}],"suggested_unified_diffs":[{"target":"Settings seed-phrase handler (map to the actual source component; no public clone was available)","diff":"- await client.auth.updateMe({ seed_phrase: phrase })\n- toast.success('Seed phrase saved')\n+ throw new Error('Hosted seed storage is disabled')\n+ // Keep seed material out of network requests and remote entities."},{"target":"Primary-address settings handler","diff":"- const generated = await client.integrations.Core.InvokeLLM({ prompt: 'Generate a realistic-looking Monero wallet address...' })\n- setCandidateAddress(generated.address)\n+ const { address } = await walletRPC('get_address')\n+ assertValidMoneroAddress(address, expectedNetwork)\n+ setCandidateAddress(address)"},{"target":"Send transaction component","diff":"- <button onClick={submitTransfer}>NEXT</button>\n+ <button onClick={() => setReviewOpen(true)}>REVIEW</button>\n+ <TransferReview address={address} amount={amount} fee={fee} onConfirm={async pin => {\n+   await verifyLocalPin(pin)\n+   await submitTransfer()\n+ }} />"},{"target":"Wallet entity persistence","diff":"- await Wallet.create({ ...form, view_key, rpc_endpoint })\n+ const safe = { label: form.label, address: validatedAddress }\n+ await Wallet.create(safe)\n+ await localEncryptedConfig.set({ rpc_endpoint: stripUrlCredentials(form.rpc_endpoint) })\n+ // Never upload view_key."},{"target":"Receive/POS QR component","diff":"- const src = `https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(uri)}`\n- return <img src={src} />\n+ const dataUrl = await QRCode.toDataURL(uri, { errorCorrectionLevel: 'M' })\n+ return <img src={dataUrl} alt='Monero payment QR' />"},{"target":"Netlify headers configuration","diff":"+ Permissions-Policy: camera=(self), microphone=(), geolocation=(), payment=(), usb=()\n+ Cross-Origin-Opener-Policy: same-origin\n  Content-Security-Policy: default-src 'self'; ..."}],"retest_plan_no_secrets":["Deploy a public proof build containing no real seed, view key, wallet credential, or RPC secret.","Use browser DevTools Network while entering synthetic seed words; assert zero requests contain seed_phrase or any seed word.","Search the built bundle and backend schema for seed_phrase and the LLM address-generation prompt; both must be absent.","Connect only a disposable testnet/stagenet wallet. Confirm a transfer cannot broadcast before review plus PIN/re-authentication.","Paste invalid, wrong-network, and checksum-invalid addresses; confirm the review/submit controls remain disabled.","Save a synthetic view key and credential-bearing RPC URL; assert neither appears in remote entity/API traffic or server logs.","Open Receive and POS with DevTools; assert QR rendering makes no request to api.qrserver.com or any third-party QR host.","Verify /manifest.json is valid JSON with application/manifest+json, or remove the manifest link; verify nonexistent worker paths return 404.","Check response headers for the existing CSP/HSTS/no-referrer controls plus the proposed Permissions-Policy.","Run a final repository secret scan only against the public source tree; do not scan or upload home directories, wallets, shell history, or credentials."],"honesty_live_vs_assumed":{"verified_live":["Fetched the live HTML and hashed public JavaScript bundle without credentials.","Observed live response headers including CSP, HSTS, Referrer-Policy, nosniff, and frame denial.","Traced public bundle code for seed storage, wallet entity persistence, walletRPC transfer, camera lifecycle, QR rendering, and SDK authentication mode.","Confirmed manifest and common service-worker paths return HTML fallback content.","Confirmed the camera scanner cleanup calls stop on success and component unmount."],"assumed_or_not_tested":["No private source repository or Bryan-provided zip was available, so source filenames in patch targets are logical component descriptions rather than claimed private paths.","No real wallet, seed, view key, RPC credential, PIN, or production transaction was used.","Backend Base44 entity authorization and walletRPC implementation were not visible; findings describe the public client behavior and required server-side controls.","No GitHub, Netlify, Base44, or production configuration was modified."]},"exfil_rules_respected":true,"contains_secrets":false,"production_changes_made":false},"attachments":[],"redactions":0,"note":"Shown as stored."}