Pragmatic Programmer – Topic 43: Stay Safe Out There — Bảo Vệ Code Trước Thế Giới Bên Ngoài

Mở đầu
Hồi mới đi làm, mình từng nghĩ "security là chuyện của mấy anh DevOps" — mình chỉ cần code chạy đúng là được. Sai lầm! Cho tới một ngày, một cái API mình viết bị ai đó gửi lên một payload kỳ quặc, database suýt nổ tung. Lúc đó mình mới thấm câu nói trong Pragmatic Programmer: "You're surrounded by sharks" — ở đâu cũng có cá mập cả.
Topic 43 — Stay Safe Out There — là lời nhắc nhở rằng: khi code của bạn chạy trên production, nó đang ở trong một thế giới đầy rẫy hiểm nguy. Malformed input, injection, DDoS, data leak — tất cả đều có thể xảy ra nếu bạn không cẩn thận.
Ảnh: Muhammed Ensar — Pexels
Nguyên tắc #1: Trust Nothing, Validate Everything
Câu đầu tiên trong sách dạy mình là: mọi input từ bên ngoài đều là kẻ thù cho tới khi được kiểm tra. Dù là query parameter, request body, file upload, hay thậm chí là dữ liệu từ database của chính mình — nếu nó đến từ bên ngoài biên giới code của bạn, hãy kiểm tra nó.
# Không làm thế này
def get_user(user_id):
return db.query("SELECT * FROM users WHERE id = " + str(user_id))
# Làm thế này
def get_user(user_id):
if not isinstance(user_id, int) or user_id <= 0:
raise ValueError("Invalid user_id")
return db.query("SELECT * FROM users WHERE id = ?", (user_id,))
Ba câu hỏi vàng cho mỗi input:
- Có đúng kiểu không? (string, int, bool...)
- Có nằm trong khoảng cho phép không? (độ dài, giá trị min/max)
- Có chứa ký tự nguy hiểm không? (SQL injection, XSS, path traversal)
Nguyên tắc #2: Principle of Least Privilege
Đây là nguyên tắc mình thấy nhiều team bỏ qua nhất. Một service chỉ nên có đúng quyền nó cần, không hơn.
Ví dụ thực tế: mình từng thấy một background job chỉ cần đọc dữ liệu nhưng lại được cấp database credential có quyền DROP TABLE. Một lỗi nhỏ trong job đó có thể xoá sạch production database.
// ❌ Quá nhiều quyền
const dbClient = new Client({
user: 'admin',
password: 'super_secret',
database: 'production'
});
// ✅ Chỉ đúng quyền cần thiết
const dbClient = new Client({
user: 'readonly_user',
password: 'limited_access',
database: 'production'
});
Áp dụng nguyên tắc này cho mọi thứ: database user, IAM role, API key, file system permission.
Ảnh: Harold Vasquez — Pexels
Nguyên tắc #3: Defense in Depth — Nhiều Lớp Bảo Vệ
Một lớp bảo vệ là không đủ. Nếu chỉ dựa vào validation ở frontend, hacker có thể gửi request thẳng tới API của bạn. Nếu chỉ dựa vào database constraint, dữ liệu xấu đã kịp đi vào hệ thống rồi.
Thiết kế nhiều lớp:
- Client-side validation — trải nghiệm người dùng tốt
- API-level validation — chặn request xấu ở gateway
- Application-level validation — kiểm tra business logic
- Database constraints — lưới an toàn cuối cùng
- Audit logging — biết chuyện gì đã xảy ra nếu có lỗi
// Mỗi lớp kiểm tra một thứ khác nhau
func CreateUser(w http.ResponseWriter, r *http.Request) {
// Lớp 1: Parse + validate input
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid request", 400)
return
}
if len(req.Email) > 255 || !strings.Contains(req.Email, "@") {
http.Error(w, "invalid email", 400)
return
}
// Lớp 2: Business logic validation
existing, _ := db.GetUserByEmail(req.Email)
if existing != nil {
http.Error(w, "email already exists", 409)
return
}
// Lớp 3: DB constraint (unique email)
user, err := db.CreateUser(req)
if err != nil {
log.Printf("DB error: %v", err) // Audit log
http.Error(w, "internal error", 500)
return
}
}
Đừng Quên Encryption
Nói về security mà quên encryption là thiếu sót lớn. Mọi dữ liệu nhạy cảm nên được encrypt cả khi truyền tải (in transit) lẫn khi lưu trữ (at rest):
- In transit: HTTPS mặc định, không chấp nhận kết nối không mã hoá
- At rest: Database encryption, encrypted file system
- Application level: Encrypt sensitive fields riêng (mật khẩu, token, PII)
Và quan trọng nhất: đừng tự viết encryption — dùng thư viện đã được kiểm chứng. Crypto là mảnh đất mà một sai lầm nhỏ cũng có thể phá huỷ toàn bộ hệ thống bảo vệ của bạn.
Ảnh: Dan Nelson — Pexels
Security là Văn Hoá, Không Phải Checklist
Điều mình tâm đắc nhất từ topic này là: security không phải là một cái checklist bạn gạch đầu dòng trước khi release. Nó là văn hoá, là tư duy phải có trong mỗi dòng code bạn viết.
Mấy câu hỏi mình tự hỏi mỗi khi code:
- "Nếu ai đó cố tình gửi input xấu, code mình có chịu được không?"
- "Có thông tin nhạy cảm nào bị log ra không?"
- "Nếu database bị hack, attacker có thể lấy được gì?"
Nói chung, bảo mật không phải là thứ bạn thêm vào sau cùng. Nó là cách bạn nghĩ về code ngay từ đầu. Còn bạn thì sao? Có bao giờ gặp thảm hoạ vì quên kiểm tra input chưa?
📋 Phụ lục thuật ngữ
- Defense in depth — chiến lược an ninh nhiều lớp, nếu một lớp bị xuyên thủng thì lớp khác vẫn còn bảo vệ
- Principle of least privilege — nguyên tắc chỉ cấp quyền tối thiểu cần thiết cho mỗi thành phần
- Input validation — kiểm tra dữ liệu đầu vào trước khi xử lý
- Encryption in transit — mã hoá dữ liệu khi truyền qua mạng (HTTPS, TLS)
- Encryption at rest — mã hoá dữ liệu khi lưu trữ trên ổ cứng/database