# Currency inputs reject negative values at validation layer

- **Pattern:** `ab-001437` (`finserv-form-validation.currency-amount.negative-amounts-blocked`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001437
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001437)

## Why it matters

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.

## Severity rationale

Critical because an unblocked negative amount on the server layer enables direct ledger manipulation, allowing an attacker to reverse funds flow without authentication.

## Remediation

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.

```ts
// 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.

## Detection

- **ID:** `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`:**
  ```html
  <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`:**
  ```ts
  const amountSchema = z.number().min(0, 'Amount must be non-negative');
  const result = amountSchema.parse(userInput);
  ```

## External references

- cwe CWE-20
- owasp A03
- pci-dss Req-6.2.4

Taxons: injection-and-input-trust, data-integrity

HTML version: https://auditbuffet.com/patterns/ab-001437
