A currency input that allows negative values on either layer hands an attacker a trivial path to credit-back fraud: submit a negative transfer amount and the ledger logic subtracts from the recipient instead of the sender. OWASP A03 (Injection) and PCI DSS Req-6.2.4 both treat unvalidated numeric inputs as critical exposure. Client-side only guards are bypassed with a two-second curl command, so the server must independently reject any value below zero before it touches business logic or database writes.
Critical because an unblocked negative amount on the server layer enables direct ledger manipulation, allowing an attacker to reverse funds flow without authentication.
Add a min="0" attribute to the HTML input and a z.number().min(0) constraint in the server route. Both layers must enforce the rule independently — client validation is a UX aid, not a security control.
// src/app/api/transfers/route.ts
const schema = z.object({
amountCents: z.number().int().min(0, 'Amount must be non-negative'),
});
const { amountCents } = schema.parse(await req.json());
For the form component in src/components/TransferForm.tsx, add type="number" min="0" step="0.01" to the input element.
ID: finserv-form-validation.currency-amount.negative-amounts-blocked
Severity: critical
What to look for: Count all currency/amount input fields across the project. For each, classify whether client-side validation (HTML min="0", regex, or JS validation) AND server-side validation (API route / controller rejecting amount < 0) are present. Quote the validation expression or attribute found (e.g., min="0" or z.number().min(0)). Report the ratio: "X of Y amount inputs have dual-layer negative rejection."
Pass criteria: At least 100% of currency/amount inputs have both client-side AND server-side validation rejecting negative values. The HTML input uses type="number" min="0" or equivalent, and server validation explicitly checks amount >= 0 before accepting the value. Report even on pass: "X of Y amount inputs validated on both layers."
Fail criteria: Any currency input allows negative amounts on either layer. A project with at least 1 amount input missing server-side negative rejection does not count as pass.
Do NOT pass when: Only client-side validation exists with no corresponding server-side check. Client-only validation is trivially bypassed.
Skip (N/A) when: The project has no currency or amount input forms (0 amount fields found after searching src/, app/, pages/, and components/).
Detail on fail: Specify which validation layer is missing. Example: "HTML amount input lacks min='0' attribute. Server validation missing check for negative amounts before insertion into database." or "Only client-side validation present; no server-side check for amount < 0"
Cross-reference: The Error Resilience audit covers broader form validation patterns and error boundaries that complement financial input validation.
Remediation: Currency amounts must always be non-negative. Add validation at both layers. In src/components/ or app/ form files, add the min attribute. In src/app/api/ or pages/api/ route handlers, add server-side rejection:
Client-side (HTML) in src/components/TransferForm.tsx:
<input
type="number"
name="amount"
min="0"
step="0.01"
placeholder="0.00"
required
/>
Server-side (Node.js/Zod) in src/app/api/transfers/route.ts:
const amountSchema = z.number().min(0, 'Amount must be non-negative');
const result = amountSchema.parse(userInput);