secure-coding-rules 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,146 @@
1
+ # Content Security Policy Rules
2
+
3
+ > Frontend Security - Content Security Policy (CSP)
4
+
5
+ ## Rules
6
+
7
+ ### 1. Implement a Strict CSP
8
+ - **DO**: Define a Content-Security-Policy header that restricts script sources. Start with `default-src 'self'` and add specific directives as needed.
9
+ - **DON'T**: Use a permissive CSP (`default-src *`) or skip CSP entirely.
10
+ - **WHY**: CSP is the strongest browser-level defense against XSS. A strict policy prevents execution of injected scripts even if an XSS vulnerability exists.
11
+
12
+ ### 2. Use Nonce-Based or Hash-Based Script Loading
13
+ - **DO**: Generate a unique cryptographic nonce per request and apply it to all `<script>` tags via `nonce` attribute. Set `script-src 'nonce-<value>'` in the CSP header.
14
+ - **DON'T**: Use `'unsafe-inline'` or `'unsafe-eval'` in `script-src`.
15
+ - **WHY**: `'unsafe-inline'` defeats the purpose of CSP. Nonces ensure only server-generated scripts execute.
16
+
17
+ ### 3. Restrict Object and Frame Sources
18
+ - **DO**: Set `object-src 'none'` and `frame-ancestors 'self'` (or specific origins) to prevent plugin abuse and clickjacking.
19
+ - **DON'T**: Allow unrestricted embedding of your site in iframes or loading of Flash/Java plugins.
20
+ - **WHY**: Plugins can bypass other security controls. Unrestricted framing enables clickjacking attacks.
21
+
22
+ ### 4. Enable CSP Reporting
23
+ - **DO**: Set `report-uri` or `report-to` directives to collect CSP violation reports. Monitor them for XSS attempts and policy issues.
24
+ - **DON'T**: Deploy CSP without monitoring. Use `Content-Security-Policy-Report-Only` for testing before enforcement.
25
+ - **WHY**: CSP reports reveal real attack attempts and policy misconfigurations without breaking the application.
26
+
27
+ ### 5. Avoid Overly Broad Whitelists
28
+ - **DO**: Whitelist specific origins (e.g., `script-src 'self' https://cdn.specific.com`). Use `strict-dynamic` with nonces for dynamically loaded scripts.
29
+ - **DON'T**: Whitelist entire CDN domains like `*.cloudflare.com` or `*.googleapis.com` that host arbitrary user content.
30
+ - **WHY**: Broad domain whitelists can be bypassed using JSONP endpoints or user-uploaded scripts on those domains.
31
+
32
+ ### 6. Apply CSP to All Response Types
33
+ - **DO**: Set CSP headers on HTML responses, error pages, and API responses that might be rendered by browsers.
34
+ - **DON'T**: Only apply CSP to your main pages, leaving error pages, login pages, or admin panels unprotected.
35
+ - **WHY**: Attackers target pages without CSP protection. Consistent application ensures no gaps.
36
+
37
+ ## Code Examples
38
+
39
+ ### Bad Practice
40
+ ```javascript
41
+ // Permissive CSP that provides minimal protection
42
+ app.use((req, res, next) => {
43
+ res.setHeader("Content-Security-Policy", "default-src *; script-src * 'unsafe-inline' 'unsafe-eval'");
44
+ next();
45
+ });
46
+
47
+ // Inline script without nonce (blocked by strict CSP)
48
+ // <script>doSomething();</script>
49
+ ```
50
+
51
+ ### Good Practice
52
+ ```javascript
53
+ import crypto from "node:crypto";
54
+
55
+ // Strict nonce-based CSP middleware
56
+ function cspMiddleware(req, res, next) {
57
+ const nonce = crypto.randomBytes(16).toString("base64");
58
+ res.locals.cspNonce = nonce;
59
+
60
+ const csp = [
61
+ `default-src 'self'`,
62
+ `script-src 'nonce-${nonce}' 'strict-dynamic'`,
63
+ `style-src 'self' 'nonce-${nonce}'`,
64
+ `img-src 'self' data: https:`,
65
+ `font-src 'self'`,
66
+ `connect-src 'self' https://api.example.com`,
67
+ `frame-ancestors 'self'`,
68
+ `object-src 'none'`,
69
+ `base-uri 'self'`,
70
+ `form-action 'self'`,
71
+ `report-uri /csp-report`,
72
+ ].join("; ");
73
+
74
+ res.setHeader("Content-Security-Policy", csp);
75
+ next();
76
+ }
77
+
78
+ app.use(cspMiddleware);
79
+
80
+ // CSP violation report endpoint
81
+ app.post("/csp-report", express.json({ type: "application/csp-report" }), (req, res) => {
82
+ const report = req.body["csp-report"];
83
+ logger.warn({
84
+ type: "csp_violation",
85
+ blockedUri: report["blocked-uri"],
86
+ violatedDirective: report["violated-directive"],
87
+ documentUri: report["document-uri"],
88
+ sourceFile: report["source-file"],
89
+ lineNumber: report["line-number"],
90
+ });
91
+ res.sendStatus(204);
92
+ });
93
+
94
+ // HTML template using nonce
95
+ function renderPage(res, content) {
96
+ return `
97
+ <!DOCTYPE html>
98
+ <html>
99
+ <head>
100
+ <meta charset="utf-8">
101
+ <link rel="stylesheet" href="/styles/main.css" nonce="${res.locals.cspNonce}">
102
+ </head>
103
+ <body>
104
+ ${content}
105
+ <script nonce="${res.locals.cspNonce}" src="/js/app.js"></script>
106
+ </body>
107
+ </html>
108
+ `;
109
+ }
110
+
111
+ // Next.js CSP configuration (next.config.js)
112
+ const nextConfig = {
113
+ async headers() {
114
+ return [
115
+ {
116
+ source: "/(.*)",
117
+ headers: [
118
+ {
119
+ key: "Content-Security-Policy",
120
+ value: [
121
+ "default-src 'self'",
122
+ "script-src 'self' 'unsafe-inline'", // Next.js requires unsafe-inline; use nonce in custom server
123
+ "style-src 'self' 'unsafe-inline'",
124
+ "img-src 'self' data: https:",
125
+ "font-src 'self'",
126
+ "object-src 'none'",
127
+ "frame-ancestors 'self'",
128
+ ].join("; "),
129
+ },
130
+ ],
131
+ },
132
+ ];
133
+ },
134
+ };
135
+ ```
136
+
137
+ ## Quick Checklist
138
+ - [ ] CSP header set on all HTML responses
139
+ - [ ] `default-src 'self'` as baseline
140
+ - [ ] `script-src` uses nonces or hashes (no `'unsafe-inline'` or `'unsafe-eval'`)
141
+ - [ ] `object-src 'none'` set to block plugins
142
+ - [ ] `frame-ancestors` set to prevent clickjacking
143
+ - [ ] `base-uri 'self'` to prevent base tag hijacking
144
+ - [ ] CSP reporting configured and monitored
145
+ - [ ] `Content-Security-Policy-Report-Only` used for testing before enforcement
146
+ - [ ] No overly broad CDN domain whitelists
@@ -0,0 +1,151 @@
1
+ # CSRF Protection Security Rules
2
+
3
+ > Frontend Security - Cross-Site Request Forgery (CSRF) Defense
4
+
5
+ ## Rules
6
+
7
+ ### 1. Implement Anti-CSRF Tokens
8
+ - **DO**: Generate a unique, unpredictable CSRF token per session or per request. Include it in every state-changing request.
9
+ - **DON'T**: Rely on cookies alone for authentication of state-changing requests.
10
+ - **WHY**: CSRF attacks exploit the browser's automatic cookie inclusion. Tokens add a second factor that attackers cannot obtain cross-origin.
11
+
12
+ ### 2. Use SameSite Cookie Attribute
13
+ - **DO**: Set `SameSite=Strict` for session cookies where possible, or `SameSite=Lax` as a minimum.
14
+ - **DON'T**: Use `SameSite=None` without `Secure` flag, or omit the SameSite attribute entirely.
15
+ - **WHY**: SameSite prevents browsers from sending cookies with cross-origin requests, providing a strong defense-in-depth layer.
16
+
17
+ ### 3. Verify Origin and Referer Headers
18
+ - **DO**: On the server, validate `Origin` or `Referer` headers match your domain for state-changing requests.
19
+ - **DON'T**: Accept requests without origin validation, especially for API endpoints that modify data.
20
+ - **WHY**: Origin/Referer verification is a supplementary defense that blocks cross-origin form submissions and fetch requests.
21
+
22
+ ### 4. Use Custom Request Headers for APIs
23
+ - **DO**: Require a custom header (e.g., `X-Requested-With`) for all API requests. Simple forms cannot set custom headers.
24
+ - **DON'T**: Accept state-changing requests that could originate from simple HTML forms without additional verification.
25
+ - **WHY**: Browsers enforce that cross-origin requests with custom headers trigger a CORS preflight, which fails unless explicitly allowed.
26
+
27
+ ### 5. Separate Read and Write Operations
28
+ - **DO**: Use GET only for read operations. Use POST, PUT, PATCH, DELETE for state-changing operations.
29
+ - **DON'T**: Use GET requests for actions that modify data (e.g., `/api/delete-account?confirm=true`).
30
+ - **WHY**: GET requests can be triggered by `<img>` tags, links, and redirects, making them trivially exploitable for CSRF.
31
+
32
+ ### 6. Implement Double-Submit Cookie Pattern When Stateless
33
+ - **DO**: Set a random value in a cookie and require the same value in a request header or body. Compare both server-side.
34
+ - **DON'T**: Use a predictable or static value for the double-submit token.
35
+ - **WHY**: This pattern works without server-side token storage, suitable for stateless APIs and SPAs.
36
+
37
+ ## Code Examples
38
+
39
+ ### Bad Practice
40
+ ```javascript
41
+ // State-changing GET request
42
+ app.get("/api/transfer", authenticate, async (req, res) => {
43
+ await transferFunds(req.user.id, req.query.to, req.query.amount);
44
+ res.json({ success: true });
45
+ });
46
+
47
+ // No CSRF protection on form submission
48
+ app.post("/api/change-email", authenticate, async (req, res) => {
49
+ await db.updateEmail(req.user.id, req.body.email);
50
+ res.json({ success: true });
51
+ });
52
+
53
+ // SameSite not set
54
+ res.cookie("session", token, { httpOnly: true, secure: true }); // Missing SameSite
55
+ ```
56
+
57
+ ### Good Practice
58
+ ```javascript
59
+ import crypto from "node:crypto";
60
+
61
+ // CSRF token generation and validation middleware
62
+ function csrfProtection() {
63
+ return (req, res, next) => {
64
+ // Generate token for GET requests
65
+ if (req.method === "GET") {
66
+ const token = crypto.randomBytes(32).toString("hex");
67
+ req.session.csrfToken = token;
68
+ res.locals.csrfToken = token;
69
+ return next();
70
+ }
71
+
72
+ // Validate token for state-changing requests
73
+ const token = req.headers["x-csrf-token"] ?? req.body._csrf;
74
+ if (!token || token !== req.session.csrfToken) {
75
+ return res.status(403).json({ error: "Invalid CSRF token" });
76
+ }
77
+ next();
78
+ };
79
+ }
80
+
81
+ // Apply CSRF protection
82
+ app.use(csrfProtection());
83
+
84
+ // Secure cookie with SameSite
85
+ res.cookie("session", token, {
86
+ httpOnly: true,
87
+ secure: true,
88
+ sameSite: "strict",
89
+ maxAge: 3600_000,
90
+ path: "/",
91
+ });
92
+
93
+ // Origin validation middleware
94
+ function validateOrigin(allowedOrigins) {
95
+ return (req, res, next) => {
96
+ if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next();
97
+ const origin = req.headers.origin ?? req.headers.referer;
98
+ if (!origin) {
99
+ return res.status(403).json({ error: "Missing origin header" });
100
+ }
101
+ const requestOrigin = new URL(origin).origin;
102
+ if (!allowedOrigins.includes(requestOrigin)) {
103
+ return res.status(403).json({ error: "Invalid origin" });
104
+ }
105
+ next();
106
+ };
107
+ }
108
+
109
+ app.use(validateOrigin(["https://myapp.example.com"]));
110
+
111
+ // Frontend: Include CSRF token in requests
112
+ async function securePost(url, data) {
113
+ const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
114
+ return fetch(url, {
115
+ method: "POST",
116
+ credentials: "same-origin",
117
+ headers: {
118
+ "Content-Type": "application/json",
119
+ "X-CSRF-Token": csrfToken,
120
+ "X-Requested-With": "fetch",
121
+ },
122
+ body: JSON.stringify(data),
123
+ });
124
+ }
125
+
126
+ // Double-submit cookie pattern for SPAs
127
+ function doubleSubmitCsrf() {
128
+ return (req, res, next) => {
129
+ if (["GET", "HEAD", "OPTIONS"].includes(req.method)) {
130
+ const token = crypto.randomBytes(32).toString("hex");
131
+ res.cookie("csrf", token, { sameSite: "strict", secure: true });
132
+ return next();
133
+ }
134
+ const cookieToken = req.cookies.csrf;
135
+ const headerToken = req.headers["x-csrf-token"];
136
+ if (!cookieToken || cookieToken !== headerToken) {
137
+ return res.status(403).json({ error: "CSRF validation failed" });
138
+ }
139
+ next();
140
+ };
141
+ }
142
+ ```
143
+
144
+ ## Quick Checklist
145
+ - [ ] CSRF tokens included in all state-changing requests
146
+ - [ ] Session cookies set with `SameSite=Strict` or `SameSite=Lax`
147
+ - [ ] `Origin` / `Referer` headers validated server-side
148
+ - [ ] GET requests never used for state-changing operations
149
+ - [ ] Custom headers required for API requests (`X-Requested-With`)
150
+ - [ ] CSRF tokens are cryptographically random and per-session
151
+ - [ ] Frontend fetch/axios includes CSRF token in headers
@@ -0,0 +1,167 @@
1
+ # Secure State Management and DOM Security Rules
2
+
3
+ > Frontend Security - Safe State Management and DOM Manipulation
4
+
5
+ ## Rules
6
+
7
+ ### 1. Never Store Sensitive Data in Client-Side State
8
+ - **DO**: Store only non-sensitive UI state in the browser. Keep tokens in `HttpOnly` cookies managed by the server.
9
+ - **DON'T**: Store JWTs, API keys, passwords, or PII in `localStorage`, `sessionStorage`, Redux/Zustand stores, or global variables.
10
+ - **WHY**: All client-side storage is accessible via XSS. A single XSS vulnerability exposes all data in `localStorage` and JavaScript state.
11
+
12
+ ### 2. Sanitize Data Before DOM Insertion
13
+ - **DO**: Use `textContent` for text, framework bindings for dynamic content, and DOMPurify for user-generated HTML.
14
+ - **DON'T**: Use `innerHTML`, `outerHTML`, `document.write()`, or jQuery's `.html()` with any user-influenced data.
15
+ - **WHY**: Unsafe DOM APIs parse strings as HTML, enabling XSS through injected `<script>` tags, event handlers, or malicious attributes.
16
+
17
+ ### 3. Validate postMessage Communication
18
+ - **DO**: Always check `event.origin` against an allowlist. Validate the message schema before processing.
19
+ - **DON'T**: Accept messages from any origin or use `targetOrigin: "*"` when sending sensitive data.
20
+ - **WHY**: Without origin validation, any page can send messages to your application, enabling XSS and data injection attacks.
21
+
22
+ ### 4. Avoid Exposing Sensitive State in URLs
23
+ - **DO**: Use POST requests for sensitive data. Keep tokens and PII out of URL parameters and fragments.
24
+ - **DON'T**: Pass tokens, session IDs, or sensitive data in URL query strings or hash fragments.
25
+ - **WHY**: URLs are logged in browser history, server logs, referrer headers, and analytics tools, leaking sensitive data.
26
+
27
+ ### 5. Implement Immutable State Updates
28
+ - **DO**: Use immutable update patterns (spread operator, `structuredClone()`, Immer). Never mutate state directly.
29
+ - **DON'T**: Modify state objects directly or share mutable references between components.
30
+ - **WHY**: Mutable state causes unpredictable UI updates, race conditions, and can mask security-relevant state changes (e.g., permission levels).
31
+
32
+ ### 6. Secure Third-Party Widget Integration
33
+ - **DO**: Load third-party widgets in sandboxed `<iframe>` elements with minimal `allow` attributes. Use `sandbox` attribute.
34
+ - **DON'T**: Load third-party scripts directly into your page's DOM context.
35
+ - **WHY**: Third-party scripts have full access to the page's DOM, cookies, and state. A compromised widget compromises your entire application.
36
+
37
+ ### 7. Clear Sensitive State on Logout and Navigation
38
+ - **DO**: Clear all sensitive in-memory state, revoke tokens, and invalidate sessions when users log out.
39
+ - **DON'T**: Leave sensitive data in memory or storage after logout or session expiry.
40
+ - **WHY**: Residual state can be accessed by subsequent users on shared devices or by malicious scripts after session expiry.
41
+
42
+ ## Code Examples
43
+
44
+ ### Bad Practice
45
+ ```javascript
46
+ // Storing JWT in localStorage
47
+ localStorage.setItem("authToken", response.token);
48
+
49
+ // Direct DOM manipulation with user data
50
+ document.getElementById("bio").innerHTML = user.bio;
51
+
52
+ // postMessage without origin check
53
+ window.addEventListener("message", (event) => {
54
+ updateProfile(event.data); // Accepts from any origin
55
+ });
56
+
57
+ // Sending sensitive data via postMessage to any origin
58
+ parent.postMessage({ token: authToken }, "*");
59
+
60
+ // Sensitive data in URL
61
+ window.location.href = `/dashboard?token=${authToken}&ssn=${user.ssn}`;
62
+
63
+ // Mutating state directly
64
+ state.user.role = "admin"; // Direct mutation
65
+ ```
66
+
67
+ ### Good Practice
68
+ ```javascript
69
+ // Auth tokens in HttpOnly cookies (set by server, not accessible via JS)
70
+ // Server-side:
71
+ res.cookie("session", sessionToken, {
72
+ httpOnly: true,
73
+ secure: true,
74
+ sameSite: "strict",
75
+ maxAge: 3600_000,
76
+ });
77
+
78
+ // Client-side: cookies are sent automatically, no JS storage needed
79
+ async function fetchProfile() {
80
+ return fetch("/api/profile", { credentials: "same-origin" });
81
+ }
82
+
83
+ // Safe DOM update
84
+ document.getElementById("bio").textContent = user.bio;
85
+
86
+ // Secure postMessage with origin validation
87
+ const TRUSTED_ORIGINS = new Set(["https://trusted-widget.example.com"]);
88
+
89
+ window.addEventListener("message", (event) => {
90
+ if (!TRUSTED_ORIGINS.has(event.origin)) return;
91
+ const schema = z.object({ type: z.string(), payload: z.unknown() });
92
+ const result = schema.safeParse(event.data);
93
+ if (!result.success) return;
94
+ handleValidatedMessage(result.data);
95
+ });
96
+
97
+ // Send with specific target origin
98
+ widgetFrame.contentWindow.postMessage(
99
+ { type: "theme", value: "dark" },
100
+ "https://trusted-widget.example.com" // Specific origin, not "*"
101
+ );
102
+
103
+ // Immutable state updates (React example)
104
+ function userReducer(state, action) {
105
+ switch (action.type) {
106
+ case "UPDATE_PROFILE":
107
+ return { ...state, profile: { ...state.profile, ...action.payload } };
108
+ case "LOGOUT":
109
+ return { ...initialState }; // Reset to clean state
110
+ default:
111
+ return state;
112
+ }
113
+ }
114
+
115
+ // Sandboxed third-party widget
116
+ function ThirdPartyWidget({ src }) {
117
+ return (
118
+ <iframe
119
+ src={src}
120
+ sandbox="allow-scripts allow-same-origin"
121
+ referrerPolicy="no-referrer"
122
+ loading="lazy"
123
+ style={{ border: "none" }}
124
+ title="Widget"
125
+ />
126
+ );
127
+ }
128
+
129
+ // Comprehensive logout
130
+ async function secureLogout() {
131
+ await fetch("/api/logout", { method: "POST", credentials: "same-origin" });
132
+ // Clear any client-side state
133
+ sessionStorage.clear();
134
+ // Reset application state
135
+ store.dispatch({ type: "RESET" });
136
+ // Navigate to prevent back-button access
137
+ window.location.replace("/login");
138
+ }
139
+
140
+ // Clear sensitive data from memory (for sensitive forms)
141
+ function SecureForm() {
142
+ const [formData, setFormData] = useState({ cardNumber: "", cvv: "" });
143
+
144
+ async function handleSubmit(e) {
145
+ e.preventDefault();
146
+ try {
147
+ await submitPayment(formData);
148
+ } finally {
149
+ setFormData({ cardNumber: "", cvv: "" }); // Clear immediately after use
150
+ }
151
+ }
152
+
153
+ return <form onSubmit={handleSubmit}>{/* form fields */}</form>;
154
+ }
155
+ ```
156
+
157
+ ## Quick Checklist
158
+ - [ ] No JWTs, API keys, or PII in `localStorage` or `sessionStorage`
159
+ - [ ] Auth tokens stored in `HttpOnly`, `Secure`, `SameSite` cookies
160
+ - [ ] `textContent` used instead of `innerHTML` for user data
161
+ - [ ] `postMessage` handlers validate `event.origin` against allowlist
162
+ - [ ] `postMessage` sends specify exact `targetOrigin` (not `"*"`)
163
+ - [ ] No sensitive data in URL parameters or hash fragments
164
+ - [ ] Immutable state update patterns used consistently
165
+ - [ ] Third-party widgets loaded in sandboxed iframes
166
+ - [ ] All sensitive state cleared on logout
167
+ - [ ] Shared device considerations addressed (no residual state)
@@ -0,0 +1,125 @@
1
+ # XSS Prevention Security Rules
2
+
3
+ > Frontend Security - Cross-Site Scripting (XSS) Defense
4
+
5
+ ## Rules
6
+
7
+ ### 1. Never Use `innerHTML` or `dangerouslySetInnerHTML` with User Input
8
+ - **DO**: Use `textContent` for plain text insertion. Use a trusted sanitization library (DOMPurify) when HTML rendering is absolutely necessary.
9
+ - **DON'T**: Assign user-controlled strings to `innerHTML`, `outerHTML`, or React's `dangerouslySetInnerHTML`.
10
+ - **WHY**: Direct HTML insertion is the primary XSS vector. Any unsanitized markup can execute arbitrary JavaScript in the user's browser.
11
+
12
+ ### 2. Leverage Framework Auto-Escaping
13
+ - **DO**: Use React JSX expressions `{variable}`, Vue template interpolation `{{ variable }}`, or Angular template binding `{{ variable }}` which auto-escape by default.
14
+ - **DON'T**: Bypass framework escaping mechanisms unless absolutely necessary and with sanitized input.
15
+ - **WHY**: Modern frameworks escape output by default, preventing the majority of XSS attacks without extra effort.
16
+
17
+ ### 3. Sanitize Rich Text and Markdown
18
+ - **DO**: Use DOMPurify with a strict allowlist of tags and attributes when rendering user-generated HTML or Markdown.
19
+ - **DON'T**: Trust Markdown-to-HTML conversion output without post-sanitization. Never allow `<script>`, `<iframe>`, event handlers, or `javascript:` URIs.
20
+ - **WHY**: Markdown parsers may produce raw HTML. Even "safe" tags can carry malicious attributes like `onerror` or `onload`.
21
+
22
+ ### 4. Validate and Sanitize URLs
23
+ - **DO**: Validate that user-provided URLs use `https:` or `http:` protocols. Reject `javascript:`, `data:`, and `vbscript:` URIs.
24
+ - **DON'T**: Render user-supplied URLs in `href`, `src`, or `action` attributes without protocol validation.
25
+ - **WHY**: `javascript:` URIs execute code when clicked or loaded, bypassing typical XSS defenses.
26
+
27
+ ### 5. Avoid Dynamic Code Execution
28
+ - **DO**: Use static templates and data binding. Parse JSON with `JSON.parse()`.
29
+ - **DON'T**: Use `eval()`, `Function()`, `setTimeout(string)`, or `setInterval(string)` with any data derived from user input.
30
+ - **WHY**: Dynamic code execution converts data into code, the fundamental cause of injection attacks.
31
+
32
+ ### 6. Escape Data in Non-HTML Contexts
33
+ - **DO**: Apply context-specific encoding when inserting data into JavaScript strings, CSS values, or URL parameters.
34
+ - **DON'T**: Assume HTML escaping is sufficient for all contexts. Each context (JS, CSS, URL) needs its own encoding.
35
+ - **WHY**: HTML-encoded data inserted into a `<script>` block or inline CSS can still execute as code.
36
+
37
+ ### 7. Implement DOM-Based XSS Prevention
38
+ - **DO**: Treat all client-side data sources (URL parameters, `location.hash`, `document.referrer`, `postMessage`) as untrusted. Validate before use.
39
+ - **DON'T**: Read from `window.location`, `document.cookie`, or `localStorage` and insert directly into the DOM.
40
+ - **WHY**: DOM-based XSS occurs entirely in the browser without server involvement, bypassing server-side sanitization.
41
+
42
+ ## Code Examples
43
+
44
+ ### Bad Practice
45
+ ```javascript
46
+ // Direct innerHTML with user input
47
+ document.getElementById("output").innerHTML = userComment;
48
+
49
+ // React dangerouslySetInnerHTML without sanitization
50
+ function Comment({ body }) {
51
+ return <div dangerouslySetInnerHTML={{ __html: body }} />;
52
+ }
53
+
54
+ // javascript: URI in href
55
+ function UserLink({ url, label }) {
56
+ return <a href={url}>{label}</a>; // url could be "javascript:alert(1)"
57
+ }
58
+
59
+ // DOM-based XSS via URL hash
60
+ const name = decodeURIComponent(window.location.hash.slice(1));
61
+ document.getElementById("greeting").innerHTML = `Hello, ${name}!`;
62
+
63
+ // eval with user data
64
+ const config = eval(`(${searchParams.get("config")})`);
65
+ ```
66
+
67
+ ### Good Practice
68
+ ```javascript
69
+ import DOMPurify from "dompurify";
70
+
71
+ // Safe text insertion
72
+ document.getElementById("output").textContent = userComment;
73
+
74
+ // React with DOMPurify for rich content
75
+ function Comment({ body }) {
76
+ const sanitized = DOMPurify.sanitize(body, {
77
+ ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p", "br", "ul", "ol", "li"],
78
+ ALLOWED_ATTR: ["href", "title"],
79
+ });
80
+ return <div dangerouslySetInnerHTML={{ __html: sanitized }} />;
81
+ }
82
+
83
+ // Safe URL validation
84
+ function isValidUrl(url) {
85
+ try {
86
+ const parsed = new URL(url);
87
+ return ["https:", "http:"].includes(parsed.protocol);
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+
93
+ function UserLink({ url, label }) {
94
+ const safeUrl = isValidUrl(url) ? url : "#";
95
+ return <a href={safeUrl} rel="noopener noreferrer">{label}</a>;
96
+ }
97
+
98
+ // Safe DOM-based rendering
99
+ const name = decodeURIComponent(window.location.hash.slice(1));
100
+ const greeting = document.getElementById("greeting");
101
+ greeting.textContent = `Hello, ${name}!`; // textContent, not innerHTML
102
+
103
+ // Safe postMessage handling
104
+ window.addEventListener("message", (event) => {
105
+ if (event.origin !== "https://trusted-domain.com") return; // Origin check
106
+ const data = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
107
+ // Validate schema before use
108
+ if (typeof data.action === "string" && typeof data.value === "string") {
109
+ handleMessage(data);
110
+ }
111
+ });
112
+
113
+ // Safe JSON parsing instead of eval
114
+ const config = JSON.parse(searchParams.get("config") ?? "{}");
115
+ ```
116
+
117
+ ## Quick Checklist
118
+ - [ ] No `innerHTML` or `dangerouslySetInnerHTML` with unsanitized user input
119
+ - [ ] DOMPurify used for any user-generated HTML/Markdown rendering
120
+ - [ ] Framework auto-escaping relied upon for standard output
121
+ - [ ] User-provided URLs validated for safe protocols (`https:`, `http:`)
122
+ - [ ] No `eval()`, `Function()`, or `setTimeout(string)` with user data
123
+ - [ ] `postMessage` handlers validate `event.origin`
124
+ - [ ] URL parameters, hash, and referrer treated as untrusted input
125
+ - [ ] Context-specific encoding applied (HTML, JS, CSS, URL contexts)