Tax/fee calculations pre-filled with server verification
Why it matters
Any fee or tax value that the server accepts directly from the client request body is tamper-able: a user reduces fee to 0 before submitting and pays less. This is OWASP A03 (Injection) and CWE-602 (Client-Side Enforcement of Server-Side Security) in one pattern. PCI DSS Req-6.2.4 requires server-side validation of all payment parameters. The client can display a fee preview for UX purposes — but the server must independently compute and verify the fee amount before it reaches the ledger.
Severity rationale
Low because exploiting this requires a user to intentionally modify a network request, limiting exposure to motivated actors with basic HTTP tooling.
Remediation
Never read fee or tax from the request body. Compute them server-side from the canonical inputs and compare against any client-submitted value if you want to surface mismatches.
// src/app/api/checkout/route.ts
export async function POST(req: Request) {
const { amountCents } = await req.json(); // only accept the base amount
// Calculate server-side
const feeCents = Math.round(amountCents * 0.02); // 2% platform fee
const taxCents = Math.round(amountCents * 0.08); // 8% tax
const totalCents = amountCents + feeCents + taxCents;
// Commit with server-calculated values only
await db.order.create({ data: { amountCents, feeCents, taxCents, totalCents } });
}
Detection
-
ID:
tax-fee-verification -
Severity:
low -
What to look for: Count all calculated fee/tax/interest display points in the UI. For each, check whether the calculation is performed client-side only, server-side only, or both (client preview + server verification). Quote the calculation expression and where it runs. Report: "X of Y calculated amounts are verified server-side before commit."
-
Pass criteria: At least 100% of fee, tax, and interest calculations are computed or verified server-side before the transaction commits. Client-side calculations are display-only previews. No more than 0 client-calculated values are trusted by the server without recalculation.
-
Fail criteria: Any fee/tax amount is calculated client-side and accepted by the server without server-side recalculation.
-
Do not pass when: The server accepts a
feeortaxfield from the client request body and uses it directly — even if the client calculation is correct, it is tamper-able. -
Skip (N/A) when: The project has no calculated fees, taxes, or interest (0 calculated amount display points found).
-
Detail on fail: Example:
"Fee is calculated in JavaScript and sent to server, which uses it directly without recalculating. User could modify the fee in network request."or"Interest is pre-filled client-side; no server-side verification before committing" -
Cross-reference: The total-recalculation check covers the broader total verification pattern; this check focuses on individual fee/tax components.
-
Remediation: Always verify calculated amounts server-side in
src/app/api/route handlers:In
src/app/api/transfers/route.ts:// Client-side: Display only (for preview) const estimatedFee = amountCents * 0.02; // 2% fee, display only // Server-side: Calculate and verify export async function POST(req) { const { amountCents, submittedFee } = req.body; const calculatedFee = amountCents * 0.02; if (submittedFee !== calculatedFee) { throw new Error('Fee mismatch. Recalculating...'); } const total = amountCents + calculatedFee; // ... commit to DB }
External references
- cwe · CWE-20 — Improper Input Validation
- cwe · CWE-602 — Client-Side Enforcement of Server-Side Security
- owasp:2021 · A03
- pci-dss:4.0 · Req-6.2.4 — Prevent common software attacks including injection and improper input validation
Taxons
History
- 2026-04-18·v1.0.0·Initial import from finserv-form-validation·automated