Security-by-design principles for developing secure applications following Hack23 ISMS Secure Development Policy
This skill ensures all code development follows security-by-design principles as defined in the Hack23 ISMS Secure Development Policy. It applies to all software development activities including web applications, APIs, infrastructure code, and scripts.
MUST:
MUST NOT:
MUST:
MUST NOT:
MUST:
MUST NOT:
MUST:
MUST NOT:
MUST:
MUST NOT:
MUST:
MUST NOT:
MUST:
MUST NOT:
MUST:
MUST NOT:
// GOOD: Allowlist validation with type checking
function validateUsername(username) {
// Define what IS allowed
const usernameRegex = /^[a-zA-Z0-9_-]{3,20}$/;
// Validate type
if (typeof username !== "string") {
throw new Error("Invalid input type");
}
// Validate format and length
if (!usernameRegex.test(username)) {
throw new Error(
"Username must be 3-20 characters and contain only letters, numbers, hyphens, and underscores",
);
}
return username;
}
// BAD: No validation
function validateUsernameBad(username) {
return username; // Accepts anything!
}
// BAD: Blocklist validation
function validateUsernameBad2(username) {
if (username.includes("<") || username.includes(">")) {
throw new Error("Invalid characters");
}
return username; // Still vulnerable to many attacks
}
const bcrypt = require("bcrypt");
// GOOD: Use strong hashing with salt
async function hashPassword(password) {
// MUST use at least 12 rounds (2^12 iterations)
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);
return hashedPassword;
}
async function verifyPassword(password, hashedPassword) {
return await bcrypt.compare(password, hashedPassword);
}
// BAD: Using weak hashing
const crypto = require("crypto");
function hashPasswordBad(password) {
// NEVER use MD5 or SHA-1 for passwords!
return crypto.createHash("md5").update(password).digest("hex");
}
// BAD: Storing plaintext
function storePasswordBad(password) {
return password; // NEVER store passwords in plaintext!
}
const session = require("express-session");
const RedisStore = require("connect-redis")(session);
// GOOD: Secure session configuration
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET, // From environment, not hardcoded
name: "sessionId", // Don't use default 'connect.sid'
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // MUST use HTTPS
httpOnly: true, // MUST prevent JavaScript access
maxAge: 1800000, // 30 minutes
sameSite: "strict", // CSRF protection
},
}),
);
// BAD: Insecure session configuration
app.use(
session({
secret: "mysecret123", // Hardcoded secret!
cookie: {
secure: false, // Allows HTTP
httpOnly: false, // Vulnerable to XSS
},
}),
);
const mysql = require("mysql2/promise");
// GOOD: Parameterized queries
async function getUserByEmail(email) {
const [rows] = await connection.execute("SELECT * FROM users WHERE email = ?", [email]);
return rows[0];
}
// BAD: String concatenation
async function getUserByEmailBad(email) {
// NEVER concatenate user input into SQL!
const query = `SELECT * FROM users WHERE email = '${email}'`;
const [rows] = await connection.execute(query);
return rows[0];
}
import logging
# GOOD: Generic error messages to users, detailed logging
def process_payment(user_id, amount):
try:
# Process payment logic
charge_credit_card(user_id, amount)
return {"success": True, "message": "Payment processed"}
except CreditCardError as e:
# Log detailed error securely
logging.error(f"Payment failed for user {user_id}: {str(e)}",
extra={"user_id": user_id, "amount": amount})
# Return generic message to user
return {"success": False, "message": "Payment processing failed. Please try again."}
except Exception as e:
# Log unexpected errors
logging.exception(f"Unexpected error processing payment for user {user_id}")
# Return generic message
return {"success": False, "message": "An error occurred. Please contact support."}
# BAD: Exposing detailed errors
def process_payment_bad(user_id, amount):
try:
charge_credit_card(user_id, amount)
return {"success": True}
except Exception as e:
# NEVER expose stack traces or system details to users!
return {"success": False, "error": str(e), "trace": traceback.format_exc()}
// GOOD: Use environment variables for secrets
require("dotenv").config();
const config = {
database: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD, // Never hardcode!
database: process.env.DB_NAME,
},
api: {
key: process.env.API_KEY,
secret: process.env.API_SECRET,
},
// Secure defaults
security: {
httpsOnly: true,
hstsEnabled: true,
debugMode: false,
},
};
// Validate required environment variables
const required = ["DB_HOST", "DB_USER", "DB_PASSWORD", "API_KEY"];
required.forEach((key) => {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
});
// BAD: Hardcoded secrets
const configBad = {
database: {
host: "localhost",
user: "admin",
password: "P@ssw0rd123", // NEVER hardcode passwords!
database: "production",
},
api: {
key: "sk_live_abcdef123456", // NEVER commit API keys!
},
};
// GOOD: Encode output before rendering
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function displayUserComment(comment) {
const safeComment = escapeHtml(comment);
document.getElementById("comment").innerHTML = safeComment;
}
// Or use a trusted library
const DOMPurify = require("dompurify");
function displayUserContent(content) {
const clean = DOMPurify.sanitize(content);
document.getElementById("content").innerHTML = clean;
}
// BAD: Direct HTML insertion
function displayUserCommentBad(comment) {
// NEVER insert unsanitized user input into HTML!
document.getElementById("comment").innerHTML = comment;
}
This skill implements requirements from:
Violations of secure development rules:
All violations must be tracked and reported in security metrics.