Start with the browser’s real trust boundary
HTML and CSS may look passive, but a browser combines markup, styles, scripts, URLs, cookies, embedded services, and user-controlled content in one powerful environment. A secure implementation starts by identifying which values are trusted, which values come from visitors or external systems, and where those values are inserted. Text, attributes, URLs, inline styles, and JavaScript are different parsing contexts and need different controls.
Use semantic HTML and fixed element and attribute names. Let the framework or template engine escape ordinary text by default. Do not build markup with string concatenation or place untrusted values in event-handler attributes, srcdoc, script blocks, or style blocks. If a feature genuinely needs user-authored HTML, sanitize it with a maintained library and a narrow allowlist before rendering it.
Prevent content and URL injection
Output encoding must match the destination. HTML text encoding is not sufficient for a URL, CSS value, or JavaScript string. Keep URL schemes on an allowlist—normally https: and carefully reviewed application-relative URLs—and reject javascript:, unexpected data: uses, and protocol-relative destinations. Add rel="noopener noreferrer" where a new tab opens an external page.
Avoid DOM APIs that parse strings as markup. Prefer textContent, createElement, and fixed attribute assignments. Where legacy code needs HTML, place sanitization at the boundary and do not alter the result afterward in ways that can restore dangerous markup. The OWASP cross-site scripting prevention guidance explains the controls required for each browser context.
Use browser policies as defence in depth
Deploy a Content Security Policy that reflects the site’s actual dependencies. Begin in report-only mode, remove unnecessary origins and inline execution, then enforce it after reviewing violations. A nonce- or hash-based policy is stronger than broad host allowlists. CSP reduces impact, but does not replace safe rendering and sanitization.
Also set appropriate response headers: Strict-Transport-Security after HTTPS is stable, X-Content-Type-Options: nosniff, a restrictive Referrer-Policy, a suitable Permissions-Policy, and CSP frame-ancestors rules to control framing. Review the OWASP HTTP headers guidance before deployment because an overly broad or copied policy can create a false sense of protection.
Control forms, uploads, and third-party code
Validate form data on the server even when the browser also validates it. Protect state-changing actions against cross-site request forgery, use rate limits and abuse controls, and avoid placing secrets or sensitive personal information in hidden fields. Cookies that protect a session should use Secure, HttpOnly, and an appropriate SameSite value.
Treat uploads as hostile. Restrict type and size, generate storage names rather than trusting supplied filenames, store files outside executable paths, scan where appropriate, and serve downloads with safe content types. For third-party analytics, chat, payment, tag-manager, and form scripts, document the business owner, data collected, allowed domains, consent behaviour, and removal procedure. Every externally loaded script receives meaningful power in the page.
Review before launch and after change
- Confirm every untrusted value is encoded or sanitized for its actual destination.
- Inventory scripts, frames, fonts, forms, pixels, and external network calls.
- Test CSP in report-only mode before enforcing it.
- Verify headers at the production edge, not only in application code.
- Test keyboard navigation and error handling without exposing technical details.
- Recheck the site after tag-manager, CMS, plugin, and hosting changes.
Security is a maintained property of the deployed site. A clean static template can become risky when a marketing tag, form embed, or content workflow changes, so ownership and periodic review matter as much as the initial code.












