ZinTrust is built with security as a top priority, providing built-in protection against common web vulnerabilities.
ZinTrust implements a 10-layer security architecture where attackers must breach multiple independent security controls:
| Layer | Control | Location | Purpose |
|---|---|---|---|
| 1 | Security Headers | Global Middleware | HSTS, CSP, X-Frame-Options, X-Content-Type-Options |
| 2 | CORS | Global Middleware | Origin validation, preflight handling |
| 3 | Rate Limiting | Global Middleware | 100 req/min baseline (configurable per-route) |
| 4 | CSRF Protection | Global Middleware | Double Submit Cookie pattern |
| 5 | XSS Sanitization | Global Middleware | Recursive HTML stripping via Xss.sanitize |
| 6 | Field Sanitization | Route Middleware | Type-specific input normalization via Sanitizer.* |
| 7 | Schema Validation | Route Middleware | Type checking, format validation via Validator.validate |
| 8 | Authentication | Route Middleware | JWT verification, session validation |
| 9 | Authorization | Controller Logic | Role-based access control, ownership checks |
| 10 | SQL Injection Prevention | Database Layer | Prepared statements via QueryBuilder |
- Multiple Failure Points: Each layer provides independent protection
- Attack Surface Reduction: Early rejection reduces processing overhead
- Compliance Ready: Meets SOC2, HIPAA, PCI-DSS requirements
- Observable Security: Each layer generates audit logs
- Fail-Safe Design: One layer's failure doesn't compromise others
The ORM and Query Builder use prepared statements for all queries, making your application immune to SQL injection by default.
All prepared statements are automatically parameterized—user input is never concatenated into SQL.
export interface IQueryBuilder {
where(column: string, operator: string, value?: unknown): IQueryBuilder;
whereIn(column: string, values: unknown[]): IQueryBuilder;
whereNotIn(column: string, values: unknown[]): IQueryBuilder;
whereNull(column: string): IQueryBuilder;
whereNotNull(column: string): IQueryBuilder;
orWhere(column: string, operator: string, value?: unknown): IQueryBuilder;
select(...columns: string[]): IQueryBuilder;
get(): Promise\<Record\<string, unknown>[]>;
first(): Promise\<Record\<string, unknown> | null>;
count(): Promise\<number>;
pluck(column: string): Promise\<unknown[]>;
insert(data: Record\<string, unknown>): Promise\<number>;
update(data: Record\<string, unknown>): Promise\<number>;
delete(): Promise\<number>;
}ZinTrust includes a CsrfMiddleware that automatically verifies CSRF tokens for all state-changing requests (POST, PUT, DELETE).
By default it uses the Double Submit Cookie pattern: the server issues an XSRF-TOKEN cookie on safe requests and expects the
client to echo that value back on unsafe requests (typically via X-CSRF-Token header).
If you're building a pure Bearer-token API (no cookie-based authentication), you can bypass CSRF checks for selected routes by
configuring skipPaths:
import { CsrfMiddleware } from '@zintrust/core';
const csrf = CsrfMiddleware.create({
skipPaths: ['/api/*'],
});// In your HTML form
<input type="hidden" name="_token" value="{{ csrf_token() }}">export interface CsrfTokenData {
token: string;
sessionId: string;
createdAt: Date;
expiresAt: Date;
}
export interface ICsrfTokenManager {
generateToken(sessionId: string): string;
validateToken(sessionId: string, token: string): boolean;
invalidateToken(sessionId: string): void;
getTokenData(sessionId: string): CsrfTokenData | null;
refreshToken(sessionId: string): string | null;
cleanup(): number;
clear(): void;
getTokenCount(): number;
}The framework provides an XssProtection utility to sanitize user input and prevent XSS attacks.
import { Xss } from '@zintrust/core';
const cleanHtml = Xss.sanitize(req.body.content);export interface IXssProtection {
escape(text: string): string;
sanitize(html: string): string;
sanitizeAttribute(value: string, context?: 'href' | 'src'): string;
}
export interface IXss {
sanitize(input: unknown): unknown;
}Always use the built-in Hash utility for storing passwords:
import { Hash } from '@zintrust/core';
const hashedPassword = await Hash.make(password);
const matches = await Hash.check(password, hashedPassword);export interface IHash {
isValidHash(hash: string): boolean;
hash(plaintext: string): Promise\<string>;
hashWithRounds(plaintext: string, rounds: number): Promise\<string>;
verify(plaintext: string, hashed: string): Promise\<boolean>;
}Protect your API from brute-force attacks using the RateLimiter middleware:
Router.post(router, '/login', loginHandler, {
middleware: ['rateLimit:6:1'],
});
Router.post(router, '/bursty-endpoint', burstHandler, {
middleware: ['rateLimit:100:0.4'],
});The inline route syntax is rateLimit:<max>:<windowInMinutes>. Fractional minute windows are supported, so rateLimit:100:0.4 means 100 requests every 24 seconds.
export interface RateLimitOptions {
windowMs: number;
max: number;
message?: string;
statusCode?: number;
headers?: boolean;
keyGenerator?: (req: IRequest) => string;
store?: 'memory' | 'redis' | 'kv' | 'db';
}The Sanitizer utility provides character whitelisting to remove unwanted characters from user input.
Important: This is NOT a complete SQL injection defense. Always use parameterized queries via the ORM/QueryBuilder.
import { Sanitizer } from '@zintrust/core';
const username = Sanitizer.alphanumeric(req.body.username);
const email = Sanitizer.email(req.body.email);
const phoneClean = Sanitizer.digitsOnly(req.body.phone);Use this for normalizing identifiers, cleaning phone numbers, and reducing unexpected characters before storage/logging.
export type SanitizerType = Readonly\<{
parseAmount: (value: unknown) => number;
alphanumeric: (value: unknown) => string;
alphanumericDotDash: (value: unknown) => string;
nonNegativeNumericStringOrNull: (value: unknown) => number | null | string;
addressText: (value: unknown) => string;
emailLike: (value: unknown) => string;
email: (value: unknown) => string;
messageText: (value: unknown) => string;
numericDotOnly: (value: unknown) => string;
ipAddressText: (value: unknown) => string;
nameText: (value: unknown) => string;
alphaNumericColonDash: (value: unknown) => string;
digitsOnly: (value: unknown) => string;
decimalString: (value: unknown) => string;
dateSlash: (value: unknown) => string;
safePasswordChars: (value: unknown) => string;
wordCharsAndSpaces: (value: unknown) => string;
lowercaseAlphanumeric: (value: unknown) => string;
uppercaseAlphanumeric: (value: unknown) => string;
alphanumericNoSpaces: (value: unknown) => string;
dateSlashNoSpaces: (value: unknown) => string;
uuidTokenSafe: (value: unknown) => string;
tokenSafe: (value: unknown) => string;
keyLike: (value: unknown) => string;
}>;