| name | security-by-design |
| description | API security, MCP tool security, input validation, rate limiting, audit logging, secure authentication, and defense-in-depth principles |
| license | MIT |
Security By Design Skill
Context
This skill applies when:
- Implementing MCP protocol tools and handlers
- Exposing European Parliament data through APIs
- Handling user inputs or external data sources
- Implementing authentication or authorization mechanisms
- Processing sensitive or classified information
- Designing rate limiting or abuse prevention
- Implementing audit logging and monitoring
- Writing security-critical validation logic
- Handling errors and exceptions securely
- Configuring network security or TLS
Security is foundational, not optional. Every feature must be designed with security in mind from the start, following the principle of "secure by default" and implementing defense-in-depth strategies aligned with Hack23 AB's ISMS policies.
Rules
- Never Trust Input: Validate and sanitize all user inputs - treat everything as potentially malicious
- Fail Securely: Systems must fail to a secure state, never expose sensitive information in errors
- Principle of Least Privilege: Grant minimal permissions required for functionality, nothing more
- Defense in Depth: Implement multiple overlapping security controls (validation + rate limiting + audit logging)
- Encrypt Everything: Use TLS 1.3+ for transit, AES-256 for at rest, never store secrets in plaintext
- Audit All Actions: Log authentication attempts, authorization failures, input validation errors
- Rate Limit Aggressively: Protect against abuse, DoS attacks, and resource exhaustion
- Validate Schema: Use strict schema validation for all MCP tool inputs and outputs
- Sanitize Output: Encode data appropriately to prevent injection attacks (XSS, command injection)
- Secure Defaults: All features must be secure by default, require opt-in for permissive settings
- No Secrets in Code: Never commit API keys, tokens, or credentials to source control
- Regular Updates: Keep dependencies updated, scan for vulnerabilities continuously
- Assume Breach: Design systems assuming attackers will get in - limit blast radius
- Document Security: Reference ISMS policies, document threat models and security controls
- Test Security: Write tests for authentication, authorization, input validation, and error handling
Examples
✅ Good Pattern: Comprehensive Input Validation
export class InputValidationService {
private readonly MAX_KEYWORD_LENGTH = 200;
private readonly MAX_DOCUMENT_ID_LENGTH = 50;
private readonly MAX_ARRAY_SIZE = 100;
private readonly SAFE_KEYWORD_PATTERN = /^[a-zA-Z0-9\s\-_]+$/;
private readonly DOCUMENT_ID_PATTERN = /^EP-\d{8}-\d{5}$/;
private readonly DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
private readonly ALLOWED_DOCUMENT_TYPES = new Set([
'REPORT',
'RESOLUTION',
,
,
,
,
]);
(: ): {
( params !== || params === ) {
();
}
query = params <, >;
{
: .(query.),
: .(query.),
: .(query., ),
: .(query., ),
: .(query.),
};
}
(: ): {
( value !== ) {
();
}
trimmed = value.();
(trimmed. === ) {
();
}
(trimmed. > .) {
(
);
}
(!..(trimmed)) {
(
);
}
trimmed.(, );
}
(: ): | {
(value === || value === ) {
;
}
( value !== ) {
();
}
normalized = value.().();
(!..(normalized)) {
(
);
}
normalized;
}
(: , : ): | {
(value === || value === ) {
;
}
( value !== ) {
();
}
(!..(value)) {
(
);
}
date = (value);
((date.())) {
();
}
minDate = ();
maxDate = ();
maxDate.(maxDate.() + );
(date < minDate || date > maxDate) {
(
);
}
value;
}
(: ): {
(value === || value === ) {
;
}
num = (value);
(!.(num)) {
();
}
(!.(num)) {
();
}
(num < || num > ) {
();
}
num;
}
(: ): {
( value !== ) {
();
}
trimmed = value.();
(trimmed. > .) {
();
}
(!..(trimmed)) {
(
);
}
trimmed;
}
validateArray<T>(
: ,
: T,
: = .
): T[] {
(!.(value)) {
();
}
(value. === ) {
();
}
(value. > maxSize) {
(
);
}
value.( {
{
(item);
} (error) {
(
);
}
});
}
}
{
() {
(message);
. = ;
}
}
✅ Good Pattern: Rate Limiting with Multiple Strategies
export class RateLimiter {
private readonly fixedWindow = new Map<string, FixedWindowState>();
private readonly slidingWindow = new Map<string, number[]>();
private readonly MAX_REQUESTS = 100;
private readonly WINDOW_MS = 15 * 60 * 1000;
private readonly BURST_MAX = 10;
private readonly BURST_WINDOW_MS = 10 * 1000;
async checkLimit(clientId: ): <> {
now = .();
.(clientId, now);
.(clientId, now);
.(clientId, now);
}
(: , : ): <> {
requests = ..(clientId) || [];
recentBurst = requests.(
now - timestamp < .
);
(recentBurst. >= .) {
oldestRequest = .(...recentBurst);
retryAfter = .(
(oldestRequest + . - now) /
);
.(clientId, , recentBurst.);
(
,
retryAfter
);
}
}
(: , : ): <> {
requests = ..(clientId) || [];
recentRequests = requests.(
now - timestamp < .
);
(recentRequests. >= .) {
oldestRequest = .(...recentRequests);
retryAfter = .(
(oldestRequest + . - now) /
);
.(clientId, , recentRequests.);
(
,
retryAfter
);
}
}
(: , : ): {
requests = ..(clientId) || [];
requests.(now);
filtered = requests.(
now - timestamp < .
);
..(clientId, filtered);
}
(
: ,
: | ,
:
): <> {
event = {
: ().(),
: ,
: ,
: .(clientId),
: ,
requestCount,
: === ? . : .,
: === ? . : .,
};
auditLogger.(event);
(requestCount > . * ) {
alerting.({
: ,
: .(clientId),
: event,
});
}
}
(: ): {
crypto
.()
.(clientId)
.()
.(, );
}
(): {
( {
now = .();
( [clientId, requests] ..()) {
active = requests.(
now - timestamp < .
);
(active. === ) {
..(clientId);
} {
..(clientId, active);
}
}
}, * );
}
}
{
() {
(message);
. = ;
}
}
✅ Good Pattern: Comprehensive Audit Logging
export class AuditLogger {
private readonly logStream: fs.WriteStream;
constructor(logPath: string) {
this.logStream = fs.createWriteStream(logPath, {
flags: 'a',
encoding: 'utf8',
});
process.on('SIGTERM', () => this.close());
process.on('SIGINT', () => this.close());
}
async log(event: AuditEvent): Promise<void> {
const logEntry = {
timestamp: ().(),
: crypto.(),
: event.,
: event.,
: .(event.),
: event.,
: event.,
: event.,
: event.,
: event. ? .(event.) : ,
};
( {
..(
.(logEntry) + ,
{
(error) {
.(, error);
(error);
} {
();
}
}
);
});
}
(
: ,
: ,
?: <, >
): <> {
.({
: ,
: success ? : ,
actor,
: ,
: ,
: success ? : ,
metadata,
});
}
(
: ,
: ,
: ,
:
): <> {
.({
: ,
: granted ? : ,
actor,
action,
resource,
: granted ? : ,
});
}
(
: ,
: ,
:
): <> {
.({
: ,
: ,
actor,
: ,
: tool,
: ,
: { errorType },
});
}
(
: ,
: ,
: ,
?:
): <> {
.({
: ,
: ,
actor,
: ,
: tool,
: success ? : ,
: { duration },
});
}
(?: ): {
(!actor) {
;
}
crypto
.()
.(actor)
.()
.(, );
}
(: ): {
crypto
.()
.(ip)
.()
.(, );
}
(): {
..();
}
}
{
: ;
: | | | ;
?: ;
: ;
: ;
: | | | ;
?: <, >;
?: ;
}
✅ Good Pattern: Secure Error Handling
export class SecureErrorHandler {
constructor(
private readonly auditLogger: AuditLogger
) {}
async handleToolError(
error: Error,
context: ErrorContext
): Promise<ToolResponse> {
await this.auditLogger.log({
type: 'TOOL_ERROR',
severity: 'ERROR',
actor: context.actor,
action: context.action,
resource: context.resource,
outcome: 'FAILURE',
metadata: {
errorType: error.name,
errorMessage: error.message,
stackTrace: error.stack,
},
});
safeMessage = .(error);
{
: ,
: [{
: ,
: safeMessage,
}],
};
}
(: ): {
(error ) {
error.;
}
(error ) {
error.;
}
(error ) {
;
}
;
}
}
{
: ;
: ;
: ;
}
{
() {
(message);
. = ;
}
}
❌ Bad Pattern: No Input Validation
export async function searchDocuments(query: any): Promise<Results> {
const sql = `SELECT * FROM documents WHERE title LIKE '%${query.keywords}%'`;
return await db.query(sql);
}
export async function getDocument(params: any): Promise<Document> {
const id = params.documentId as string;
return await europeanParliamentApi.getDocument(id);
}
export async function processText(text: string): Promise<string> {
return text.toUpperCase();
}
❌ Bad Pattern: Exposing Sensitive Information
export async function handleRequest(req: Request): Promise<Response> {
try {
return await processRequest(req);
} catch (error) {
return {
error: error.message,
stack: error.stack,
file: error.fileName,
};
}
}
export function validateInput(input: string): void {
if (input.includes('<script>')) {
throw new Error(`Invalid input: ${input}`);
}
}
export async function authenticate(username: string, password: ): <> {
.();
(username, password);
}
❌ Bad Pattern: No Rate Limiting
export async function searchDocuments(query: SearchQuery): Promise<Results> {
return await europeanParliamentApi.search(query);
}
export async function handleRequests(requests: Request[]): Promise<Response[]> {
return Promise.all(requests.map(r => processRequest(r)));
}
❌ Bad Pattern: Secrets in Code
const API_KEY = 'sk-1234567890abcdef';
const config = {
database: {
host: 'db.example.com',
username: 'admin',
password: 'SuperSecret123',
},
};
const url = `https://api.example.com/data?apikey=${API_KEY}`;
References
Security Standards
ISMS Policies
Core:
Supporting:
Security Tools
Cryptography
Remember
- Never trust input: Validate everything, sanitize always, encode appropriately
- Fail securely: Default deny, safe error messages, no information leakage
- Defense in depth: Multiple overlapping security controls
- Least privilege: Minimal permissions, need-to-know basis
- Audit everything: Log security events, monitor for anomalies
- Rate limit aggressively: Protect against abuse and DoS attacks
- Encrypt always: TLS 1.3+ for transit, AES-256 for at rest
- Secure by default: All features secure unless explicitly configured otherwise
- No secrets in code: Use environment variables, secret management systems
- Update dependencies: Patch vulnerabilities quickly, scan continuously
- Assume breach: Limit blast radius, segment networks, monitor for indicators
- Document security: Reference ISMS policies, explain threat mitigations
- Test security controls: Write tests for auth, authz, validation, encryption
- European Parliament data: Respect data protection regulations, handle sensitively
- MCP protocol: Validate tool schemas strictly, sanitize responses