Skip to content

Mona#8

Open
Marvell69 wants to merge 3 commits into
monad-developers:mainfrom
Nursca:MONA
Open

Mona#8
Marvell69 wants to merge 3 commits into
monad-developers:mainfrom
Nursca:MONA

Conversation

@Marvell69
Copy link
Copy Markdown

No description provided.

@socket-security
Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn Critical
Critical CVE: Axios has Unrestricted Cloud Metadata Exfiltration via Header Injection Chain

CVE: GHSA-fvcv-3m26-pcqx Axios has Unrestricted Cloud Metadata Exfiltration via Header Injection Chain (CRITICAL)

Affected versions: < 1.15.0

Patched version: 1.15.0

From: package-lock.jsonnpm/@reown/appkit-adapter-wagmi@1.8.19npm/@reown/appkit@1.8.19npm/axios@1.13.6

ℹ Read more on: This package | This alert | What is a critical CVE?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Remove or replace dependencies that include known critical CVEs. Consumers can use dependency overrides or npm audit fix --force to remove vulnerable dependencies.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/axios@1.13.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 11, 2026

Greptile Summary

This PR introduces a full-stack AI shopping app — MONA — built on Monad Testnet, with a Gemini-powered chat interface, x402 on-chain checkout, a Zustand cart store, and a product catalog. Two blocking issues need attention before this can function correctly in production:

  • Order confirmation modal will crash at runtime: OrderConfirmationModal accesses item.product.name / item.product.priceUsdc, but order.items is CartItem[] (fields are item.productName / item.priceUsdc), causing a TypeError every time the modal opens.
  • Payment amount is client-controlled: the checkout route derives the x402 price requirement from the user-supplied ?total query parameter, allowing any user to manipulate the amount they pay.

Confidence Score: 3/5

Not safe to merge — the order confirmation modal will crash on every successful checkout, and the payment price is client-manipulable.

Two P0/P1 defects affect the primary user path: (1) the order confirmation modal throws a TypeError on every successful purchase, and (2) the x402 checkout price is sourced from a client-controlled query parameter. Both must be fixed before the app can work correctly.

src/components/OrderConfirmationModal.tsx (runtime crash) and src/app/api/checkout/route.ts (price manipulation) need immediate attention.

Security Review

  • Price manipulation (checkout route): src/app/api/checkout/route.ts sets the x402 required payment amount from the client-controlled ?total query parameter. An attacker can send any value (e.g. ?total=0.01) to pay a fraction of the real cart total. The authoritative price must be computed server-side from the submitted items.
  • No server-side cart validation: items from the request body are echoed back without verifying product IDs, quantities, or prices against the product catalog, making it trivial to fake order confirmations.

Important Files Changed

Filename Overview
src/components/OrderConfirmationModal.tsx Runtime crash: accesses item.product.name / item.product.priceUsdc but order.items is CartItem[] which has no nested product object
src/app/api/checkout/route.ts x402 payment price is sourced from the client-supplied ?total query param, enabling price manipulation; also echoes back unvalidated request body
src/components/ChatUI.tsx Stale error message references OpenAI; addItem included in useEffect dep array against project conventions; otherwise checkout and AI stream integration look correct
src/lib/checkout.ts x402 fetch wrapper and error handling are well-structured; total is passed as query param (origin of the server-side price manipulation issue)
src/app/api/chat/route.ts Gemini-backed streaming chat with searchProducts and addToCart tools; looks correct and well-structured
src/store/cartStore.ts Zustand cart store with add/remove/updateQuantity/clearCart — logic is correct; CartItem shape lacks a nested product field (source of modal bug)
src/components/Toast.tsx onDismiss in useEffect dep array may reset timer on parent re-renders; otherwise straightforward auto-dismiss toast
src/lib/monad.ts Correct Monad Testnet chain definition (id: 10143) used by the app; root-level lib/monad.ts with wrong id (41454) is dead code but confusing

Sequence Diagram

sequenceDiagram
    participant User
    participant ChatUI
    participant ChatAPI as /api/chat (Gemini)
    participant CheckoutLib as src/lib/checkout.ts
    participant CheckoutAPI as /api/checkout (x402)
    participant Blockchain as Monad Testnet

    User->>ChatUI: Types query
    ChatUI->>ChatAPI: POST /api/chat {messages}
    ChatAPI-->>ChatUI: Stream (searchProducts / addToCart tool calls)
    ChatUI->>ChatUI: addItem() → CartStore

    User->>ChatUI: Click Checkout
    ChatUI->>CheckoutLib: checkout(cart, total, walletClient)
    CheckoutLib->>CheckoutAPI: POST /api/checkout?total=X (x402 wrapped fetch)
    CheckoutAPI-->>CheckoutLib: 402 Payment Required
    CheckoutLib->>Blockchain: signTypedData → USDC transfer
    Blockchain-->>CheckoutLib: txHash
    CheckoutLib->>CheckoutAPI: POST /api/checkout?total=X + payment header
    CheckoutAPI-->>CheckoutLib: {success, txHash, items}
    CheckoutLib-->>ChatUI: OrderConfirmation
    ChatUI->>ChatUI: OrderConfirmationModal (crashes on item.product.name)
Loading

Reviews (1): Last reviewed commit: "First Commit" | Re-trigger Greptile

Comment on lines +50 to +52
<span className="text-neutral-500 mr-1">{item.quantity}x</span> {item.product.name}
</span>
<span className="text-white">${(item.product.priceUsdc * item.quantity).toFixed(2)}</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Runtime crash: item.product is undefined on CartItem

order.items is the CartItem[] array echoed back by the checkout API. CartItem has productName and priceUsdc at the top level — it has no nested product object. Accessing item.product.name and item.product.priceUsdc will throw TypeError: Cannot read properties of undefined (reading 'name') every time the modal is opened after a successful checkout.

Suggested change
<span className="text-neutral-500 mr-1">{item.quantity}x</span> {item.product.name}
</span>
<span className="text-white">${(item.product.priceUsdc * item.quantity).toFixed(2)}</span>
<span className="text-neutral-300">
<span className="text-neutral-500 mr-1">{item.quantity}x</span> {item.productName}
</span>
<span className="text-white">${(item.priceUsdc * item.quantity).toFixed(2)}</span>

Comment on lines +64 to +68
scheme: "exact",
price: (context) => {
const total = context.adapter.getQueryParam?.('total');
const priceValue = Array.isArray(total) ? total[0] : total;
return `$${priceValue || '0'}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Client-controlled price allows payment manipulation

The payment amount is read entirely from the client-supplied ?total query parameter. Any user can craft a request with ?total=0.01 and the x402 payment requirement will be set to $0.01 regardless of the real cart value. The server should derive the authoritative price from the items in the request body (summing item.priceUsdc * item.quantity) rather than trusting the client.

Comment thread src/components/ChatUI.tsx
Comment on lines +209 to +213
const errorMessage: Message = {
id: Date.now().toString(),
role: 'assistant',
parts: [{ type: 'text', text: 'Sorry, I encountered an error. Please check your OpenAI API key and credits.' }]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stale error message references OpenAI

The chat API uses Google Gemini (@ai-sdk/google, gemini-2.0-flash-001), but this catch-block tells users to check their "OpenAI API key and credits." This will confuse users and developers debugging production errors.

Suggested change
const errorMessage: Message = {
id: Date.now().toString(),
role: 'assistant',
parts: [{ type: 'text', text: 'Sorry, I encountered an error. Please check your OpenAI API key and credits.' }]
}
const errorMessage: Message = {
id: Date.now().toString(),
role: 'assistant',
parts: [{ type: 'text', text: 'Sorry, I encountered an error. Please try again.' }]
}

Comment thread src/components/ChatUI.tsx
Comment on lines +108 to +128
useEffect(() => {
const lastMessage = messages[messages.length - 1] as ExtendedMessage
if (lastMessage?.role === 'assistant' && lastMessage.toolInvocations) {
// Check for searchProducts results
const searchInv = lastMessage.toolInvocations.find(inv => inv.toolName === 'searchProducts')
if (searchInv?.state === 'result') {
setCurrentProducts(searchInv.result as Product[])
setLastSearchQuery((searchInv.args as SearchArgs).query)
setIsSearching(false)
} else if (searchInv?.state === 'call') {
setIsSearching(true)
}

// Check for addToCart results
const addInv = lastMessage.toolInvocations.find(inv => inv.toolName === 'addToCart')
if (addInv?.state === 'result' && (addInv.result as AddToCartResult).success) {
addItem((addInv.result as AddToCartResult).product, (addInv.result as AddToCartResult).quantity)
setIsCartOpen(true)
}
}
}, [messages, addItem])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 State setter addItem in useEffect dependency array

Per project conventions, state setters should not be included in useEffect dependency arrays when the effect calls that setter. While Zustand actions are generally stable references, including addItem here is inconsistent with this pattern.

Suggested change
useEffect(() => {
const lastMessage = messages[messages.length - 1] as ExtendedMessage
if (lastMessage?.role === 'assistant' && lastMessage.toolInvocations) {
// Check for searchProducts results
const searchInv = lastMessage.toolInvocations.find(inv => inv.toolName === 'searchProducts')
if (searchInv?.state === 'result') {
setCurrentProducts(searchInv.result as Product[])
setLastSearchQuery((searchInv.args as SearchArgs).query)
setIsSearching(false)
} else if (searchInv?.state === 'call') {
setIsSearching(true)
}
// Check for addToCart results
const addInv = lastMessage.toolInvocations.find(inv => inv.toolName === 'addToCart')
if (addInv?.state === 'result' && (addInv.result as AddToCartResult).success) {
addItem((addInv.result as AddToCartResult).product, (addInv.result as AddToCartResult).quantity)
setIsCartOpen(true)
}
}
}, [messages, addItem])
}, [messages])

Rule Used: Avoid including state setters in useEffect depende... (source)

Learnt From
monad-developers/monapp#251

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread lib/monad.ts
import { defineChain } from 'viem'

export const monadTestnet = defineChain({
id: 41454,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Conflicting Monad Testnet chain ID

lib/monad.ts at the repo root defines id: 41454, whereas src/lib/monad.ts (the file actually used by the application) defines id: 10143. The checkout route and x402 server both register on "eip155:10143". The root-level file appears to be dead code with a wrong chain ID, but risks being imported accidentally.

Consider removing lib/monad.ts or aligning it with the correct chain ID.

Comment thread src/components/Toast.tsx
Comment on lines +15 to +23
useEffect(() => {
setIsVisible(true);
const timer = setTimeout(() => {
setIsVisible(false);
setTimeout(onDismiss, 300); // Wait for fade out
}, duration);

return () => clearTimeout(timer);
}, [duration, onDismiss]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 onDismiss reference instability resets timer on re-render

onDismiss is passed as () => setPaymentError(null) in ChatUI, creating a new function reference on every parent render. Because onDismiss is in this effect's dependency array, any re-render of ChatUI while the toast is visible will reset the countdown timer. Wrap the caller with useCallback or omit onDismiss from the dep array since the intent is to run once on mount.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants