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.
Low because exploiting this requires a user to intentionally modify a network request, limiting exposure to motivated actors with basic HTTP tooling.
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 } });
}
ID: finserv-form-validation.calculation-accuracy.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 fee or tax field 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
}