Day 26: Security & Clean Code
✅ Today you will learn
- XSS: how attackers inject scripts – and how to stop them
- Safe rendering: textContent, escaping, sanitising
- CSP, HTTPS, cookies and third-party scripts
- Secrets and “never trust the client” (prices, discounts)
- Clean code: naming, small functions, comments, consistency
🏢 In the real world
A real pattern from small Indian e-commerce sites: the product review box rendered reviews with innerHTML. Someone posted a “review” containing a script that copied every visitor’s session token. Another site calculated the discount in JavaScript – a customer changed the price in DevTools and paid ₹1 for a gift box. Both are front-end mistakes you will never make after today.
XSS: the number one front-end vulnerability
<p>A customer review (imagine it came from the database):</p>
<div id="unsafe" style="border:2px solid #dc2626;padding:8px;border-radius:8px"></div>
<div id="safe" style="border:2px solid #16a34a;padding:8px;border-radius:8px;margin-top:8px"></div>
<p id="alarm" style="color:#b91c1c;font-weight:700"></p>
<script>
const review = 'Tasty! <img src="x" onerror="document.querySelector(\'#alarm\').textContent=\'⚠️ Attacker code ran – it could have stolen your login!\'">';
document.querySelector('#unsafe').innerHTML = '❌ innerHTML: ' + review; // executes the attacker’s code
document.querySelector('#safe').textContent = '✅ textContent: ' + review; // shown as harmless text
</script>| Sink (dangerous) | Safe alternative |
|---|---|
| el.innerHTML = userText | el.textContent = userText |
| Template literal HTML with user data | createElement + textContent, or escape() every value |
| insertAdjacentHTML / document.write | append() nodes you created |
| href = userUrl (javascript: URLs!) | Allow only http(s): new URL(u).protocol check |
| eval(), new Function(), setTimeout("string") | Never with user data – use functions and JSON.parse |
| Rich text (HTML editor content) | Sanitise with DOMPurify before inserting |
Escaping when you must build HTML strings
function escapeHTML(value) {
return String(value)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
// a tagged template that escapes every ${value} automatically
function html(strings, ...values) {
return strings.reduce((out, str, i) => out + str + (i < values.length ? escapeHTML(values[i]) : ''), '');
}
const review = { name: 'Rahul <script>steal()</script>', text: 'Best "ghewar" ever & fresh' };
console.log(html`<li><b>${review.name}</b>: ${review.text}</li>`);
function safeLink(url) {
try {
const u = new URL(url, 'https://sharmasweets.in');
return ['http:', 'https:'].includes(u.protocol) ? u.href : '#';
} catch { return '#'; }
}
console.log(safeLink('https://maps.google.com/?q=Johari+Bazaar'), safeLink('javascript:alert(1)'));Never trust the client
Done in the browser only ❌
- Price and discount sent from JS: { total: 1 }
- Coupon validity checked only in JS
- Stock checked only in JS
- “isAdmin: true” flag in localStorage
Correct design ✅
- Send only product ids and quantities
- Server looks up prices, applies coupons, checks stock
- Server calculates the total the customer pays
- Permissions checked on every server request
On Day 20 the POST body contained only { id, qty } – the API looked up prices itself, refused sold-out items and calculated delivery. Front-end validation is for user experience; the server is for truth.
Secrets, keys and tokens
| Item | Can it be in front-end JS? | Why |
|---|---|---|
| Payment gateway SECRET key | ❌ Never | Anyone can read your JS (View Source, DevTools) |
| Database password | ❌ Never | Lives only on the server |
| Payment PUBLIC/key-id (Razorpay key_id) | ✅ Yes | Designed to be public; secret stays on server |
| Google Maps browser key | ✅ With restrictions | Restrict by domain (HTTP referrer) in the console |
| Login session | Prefer HttpOnly cookie | JS cannot read it, so XSS cannot steal it |
| .env files | Only VITE_ prefixed values reach the browser | Treat them as public |
Defence in depth: CSP, HTTPS and third-party scripts
Content-Security-Policy: default-src 'self'; script-src 'self' https://checkout.razorpay.com; img-src 'self' data: https:; style-src 'self' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; connect-src 'self' https://codingeasily.in; frame-ancestors 'none' # Even if an attacker injects <script>, the browser refuses scripts not on the list.
- Serve everything over HTTPS; set cookies with Secure, HttpOnly and SameSite=Lax/Strict.
- Every third-party script (chat widgets, analytics, ad tags) can read your page – add only trusted ones, pin versions and use Subresource Integrity (integrity="sha384-…") for CDN files.
- Keep npm packages updated:
npm auditlists known vulnerabilities. - Use rel="noopener noreferrer" on links opened with target="_blank" to untrusted sites.
Clean code: write for the next developer
Before
- function calc(a, b, c) { if (c) { return a*b - a*b*0.1 + (a*b > 1500 ? 0 : 60) } else { return a*b + (a*b>1500?0:60) } }
- Magic numbers 0.1, 1500, 60
- Name says nothing
- Duplicated logic
After
- const COUPON_RATE = 0.10, FREE_DELIVERY_FROM = 1500, DELIVERY_FEE = 60;
- const subtotal = price * qty;
- const discounted = hasCoupon ? subtotal * (1 - COUPON_RATE) : subtotal;
- return discounted + (discounted >= FREE_DELIVERY_FROM ? 0 : DELIVERY_FEE);
| Habit | Example |
|---|---|
| Names reveal intent | isDeliveryFree, getProductsByCategory, MAX_CART_ITEMS |
| Functions do one thing | validateOrder(), calculateTotal(), renderCart() – not doEverything() |
| Early returns / guard clauses | if (!cart.length) return showEmpty(); |
| No magic numbers | const GST_RATE = 0.05 |
| Comments explain why, not what | // Courier refuses milk sweets – they spoil in transit |
| Consistent style | Prettier + ESLint on save (Day 21) |
| Small commits with clear messages | “Fix: delivery fee shown on free orders” |
🏅 Job skills unlocked today
Practice tasks
🏋️ Practice – do it yourself
list.innerHTML += `<li>${comment.author}: ${comment.text}</li>`.Show solution
Quick quiz
🧠 Quick Quiz – test yourself
Interview corner
💼 Interview Corner
What is XSS and how do you prevent it?
Cross-site scripting: attacker-controlled data is rendered as HTML/JS and runs in other users’ browsers. Prevent by treating data as text (textContent, framework escaping), escaping in HTML/attribute/URL contexts, sanitising rich HTML (DOMPurify), validating URLs, avoiding eval, using CSP and HttpOnly cookies.
Why should business rules not live only in the front end?
The client is fully under the user’s control – code, requests and storage can be changed. Prices, discounts, stock, permissions and payments must be enforced on the server; the front end mirrors them only for a fast, friendly experience.
Summary
- Render user data with textContent or escaping; sanitise rich HTML.
- Never trust the client: prices, stock, permissions on the server.
- No secrets in front-end code; HTTPS, CSP, HttpOnly cookies, careful third parties.
- Clean code: clear names, small functions, constants, why-comments, consistent style.
Day 27: testing and interview preparation – write automated tests and practise the most asked JavaScript questions.
