Back to blog

Secure Public Forms in Laravel: A Production Threat Model

Manuk Minasyan · · 4 min read
Secure Public Forms in Laravel: A Production Threat Model

Form security does not end when validation passes. A harmless-looking answer can become stored XSS in an admin panel, a spreadsheet formula in a CSV export, sensitive data in a log, or a payload sent to an attacker-controlled webhook destination.

The useful model is to follow one hostile submission across six trust boundaries and attach a regression test to every control.

1. Form definition to runtime schema

Treat a database-backed form schema as configuration, never executable code. Allowlist field types, rule identifiers, option-source keys, and condition operators. Bound field count, nesting, string length, option count, uploads, and expensive pattern matching.

Do not store arbitrary PHP, Blade, JavaScript, class names, SQL, or free-form regular expressions for ordinary form authors. The server should compile a small reviewed language. Build validation from the published trusted schema, not from field definitions submitted by the visitor.

2. Browser to submission

Client validation improves feedback but is bypassable. Validate and authorize on the server, allowlist request keys, and avoid mass-assigning the raw request into a model.

CSRF and spam solve different problems. Laravel request-forgery protection defends the authority attached to a browser/session; it does not prove an anonymous visitor is human. Use layered bot controls, reasonable per-form/actor rate limits, and risk-based CAPTCHA or Turnstile where needed. Per-IP-only limits can punish offices or carrier NAT while distributed bots rotate addresses.

The Laravel spam-control guide covers that layer in depth. Laravel's current CSRF documentation explains the session boundary.

3. Stored answers to every output context

Blade escaping should remain the default. A stored <script> string must display as text in the admin panel and email preview unless rich HTML is an explicit, sanitized feature.

HTML, email, Slack/Teams markup, PDF, logs, and CSV are different output contexts. Encode for the destination.

CSV/formula injection deserves special attention: spreadsheet applications can interpret cells beginning with formula markers such as =, +, -, or @. There is no universal neutralization compatible with every spreadsheet and downstream import. Define your export contract, escape or prefix dangerous values according to it, and test separators, tabs, newlines, and quote tricks. Review OWASP's CSV injection guidance.

Do not log raw form payloads, tokens, signatures, or uploaded document contents. Log identifiers, outcomes, durations, and redacted error classes.

4. Upload to private retrieval

Allowlist extensions and detected media types, generate storage names, cap count/size/total request bytes, and store outside the public webroot or on a private disk by default. Browser Content-Type and MIME validation do not prove a document is safe.

Higher-risk PDF, DOCX, image, or archive workflows may need malware scanning or content disarm/reconstruction. Protect parsers from decompression bombs and resource exhaustion. Authorize every download and prefer short-lived temporary access.

Use the full file-upload checklist for implementation details and OWASP's upload guidance for the broader threat model.

5. Form configuration to outbound webhooks

User-configured destinations create server-side request forgery risk. Restrict schemes, resolve and reject loopback/private/link-local/metadata addresses for IPv4 and IPv6, disable redirects or revalidate every redirect, and account for DNS rebinding between validation and connection.

Use short connect/total timeouts, response-size caps, and queues so a destination cannot hold the visitor request open. Encrypt and mask integration secrets; keep them out of audit diffs and logs. OWASP maintains a dedicated SSRF prevention guide.

6. Availability and privileged workflows

Enforce active state, publishing windows, tenant ownership, and capacity on the server. Hiding the form or button is not a lifecycle rule.

Put cheap bounds before expensive parsing and storage. Queue notifications, exports, and integrations. Protect individual submissions, bulk exports, secret changes, and retention actions with separate policies and audit events. An authenticated insider or compromised account can cause more damage than a spam bot.

Threat/control/test matrix

ThreatControlRegression test
Unknown payload keysAllowlisted validated dataExtra key never persists
Stored XSSContext escapingScript displays as text
Spreadsheet formulaExplicit CSV contractFormula markers stay inert
Malicious/oversized fileBounds, private storage, scanningMismatch and size cases fail
Cross-tenant selectionScoped query and validationForged tenant ID fails
Webhook SSRFPublic-address validation and egress controlsLocal/metadata/redirect cases fail
Duplicate/replayed submitIdempotency ledgerTwo requests create one effect
Log disclosureRedactionPayload and secrets absent

What not to promise

No checklist makes an application “secure” or “OWASP compliant.” Controls depend on deployed versions, infrastructure, data, and operations. Self-hosting changes who owns security and incident response; it does not automatically improve them.

The strongest article—or implementation—pairs every boundary with evidence: a policy, a deliberately hostile input, and a test proving the result. Validation is the beginning of that chain, not the end.

Related posts