| |

Node.js 56 🟢 API Security Best Practices (Helmet, CORS, Rate Limiting, Sanitization)

Securing a Node.js API means defending against a range of common threats—from cross-origin abuse and brute-force attacks to injection and data exposure. There is no single “fix” for API security; instead, you build a series of layers that each address a specific class of risk. This chapter focuses on four essential layers that belong in almost every production Express application: Helmet for security headers, CORS for controlling cross-origin access, rate limiting for abuse prevention, and input sanitization for rejecting malformed or malicious data at the boundary.

Key point: API security is about defense in depth. A misconfigured CORS policy, a missing rate limit, or an unvalidated input can each be the single point of failure that exposes your API. Every layer must be correct on its own, and together they form the perimeter that hostile requests must pass through.


Why these four layers matter

The header problem. HTTP responses can leak information and enable attacks through missing security headers. A browser can be tricked into MIME-sniffing a response, framing your API in an iframe for clickjacking, or loading resources over insecure HTTP. Helmet sets the headers that close these gaps by default.

The origin problem. Browsers enforce the Same-Origin Policy, but an API must explicitly declare which other origins are allowed to read its responses. A permissive CORS configuration—especially one that reflects any origin while allowing credentials—is functionally identical to disabling the protection entirely. Any site a logged-in user visits can then make authenticated requests to your API and read the data .

The abuse problem. Without rate limiting, an API is vulnerable to brute-force login attempts, credential stuffing, and resource exhaustion. A single attacker can flood an endpoint with thousands of requests, degrading the service for legitimate users or overwhelming downstream dependencies .

The input problem. A caller can be perfectly authenticated and still send a payload designed to break something downstream: an oversized array that exhausts memory, a string containing NoSQL operators, or a value that the business logic never expected. Authentication answers “who is this caller”; validation answers “is this payload safe to process” . The two are separate concerns, and both are required.


a. Security headers with Helmet

Helmet is a collection of middleware functions that set HTTP response headers. Calling app.use(helmet()) applies sensible defaults, and each header can be customized or disabled individually .

The basic setup

const express = require('express');
const helmet = require('helmet');

const app = express();
app.use(helmet());

This single line sets a set of headers including X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, Strict-Transport-Security, and others. The defaults are chosen to be safe for most applications, and each one can be tuned as needed .

The key headers

Strict-Transport-Security tells browsers to prefer HTTPS for a specified duration. The default max-age is 365 days, and includeSubDomains is enabled by default .

app.use(helmet({
  strictTransportSecurity: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  }
}));

Content-Security-Policy controls which sources the browser is allowed to load resources from. For an API that returns JSON rather than HTML, a strict CSP is less critical, but it still matters for any endpoint that serves HTML or redirects. A CSP with nonces is the modern approach for applications that render HTML .

X-Content-Type-Options is set to nosniff, which prevents the browser from MIME-sniffing a response away from the declared content type. This is important because MIME sniffing can turn a harmless response into an executable script .

Referrer-Policy defaults to no-referrer, which prevents the browser from sending the full URL of the current page to external sites .

Cross-Origin-Resource-Policy defaults to same-origin, which blocks other origins from loading your resources in certain contexts .

What Helmet does not do

Helmet does not validate input, authenticate users, or limit request rates. It is a header-setting middleware and nothing more. Treating it as a complete security solution is a category error .


b. CORS configuration

The cors middleware controls which origins are allowed to read your API’s responses. The most important distinction is between a public API that does not use credentials and a private API that does. For a public, credential-free API, origin: '*' with credentials: false is acceptable. For anything involving cookies or Authorization headers sent automatically by the browser, an explicit allowlist is required .

The safe allowlist pattern

const cors = require('cors');

const allowedOrigins = new Set([
  'https://app.example.com',
  'https://admin.example.com',
  process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
].filter(Boolean));

app.use(cors({
  origin: (origin, callback) => {
    // Allow requests with no origin (server-to-server, mobile apps)
    if (!origin || allowedOrigins.has(origin)) {
      return callback(null, true);
    }
    return callback(new Error('Origin not allowed by CORS'));
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization', 'X-API-Key'],
}));

The allowlist is an exact-match set. A regex like /example\.com$/ would also match example.com.evil.com, which defeats the purpose .

The dangerous pattern

// DANGEROUS: reflects any origin and allows credentials
app.use(cors({
  origin: (origin, callback) => callback(null, true),
  credentials: true,
}));

This tells the browser that every origin is trusted and that cookies should be included. Any malicious site a logged-in user visits can make authenticated requests to your API and read the responses .

The subdomain and preview-environment problem

Real applications have dynamic origins: preview deployments, per-tenant subdomains, staging hosts. The temptation is to loosen the check to a pattern. Resist it. Prefer an explicit list built from configuration, and if a pattern is unavoidable, anchor it tightly .


c. Rate limiting

The express-rate-limit package is the standard middleware for rate limiting in Express. It counts requests per client key and rejects requests that exceed the configured limit within a time window .

The basic setup

const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // 100 requests per window
  standardHeaders: true,
  legacyHeaders: false,
  message: 'Too many requests, please try again later',
});

app.use('/api/', limiter);

The standardHeaders: true option returns the rate limit information in RateLimit-* headers, which is the modern standard .

The auth-specific limiter

Authentication endpoints need a stricter limit. A brute-force attack against a login endpoint benefits from a low max value, while legitimate users are unlikely to exceed it .

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true, // Only count failed attempts
  message: 'Too many login attempts, please try again later',
});

app.post('/api/auth/login', authLimiter, loginHandler);
app.post('/api/auth/register', authLimiter, registerHandler);

The skipSuccessfulRequests: true option means that only failed login attempts count against the limit, so a user who logs in correctly is not penalized .

The distributed store

The default in-memory store does not work in a horizontally scaled deployment. If three instances each track their own counters, a client can effectively triple its allowed rate by getting distributed across nodes. A shared store, typically Redis, is required .

const RedisStore = require('rate-limit-redis');
const { createClient } = require('redis');

const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

const limiter = rateLimit({
  store: new RedisStore({
    client: redisClient,
    prefix: 'rl:',
  }),
  windowMs: 15 * 60 * 1000,
  max: 100,
});

The Redis store ensures that every instance decrements the same counter for the same client key .

The key generator

Keying purely on source IP breaks for clients behind NAT or corporate proxies. Authenticated APIs should key on the caller’s token subject or API key wherever one is available, falling back to IP only for unauthenticated routes .

const limiter = rateLimit({
  keyGenerator: (req) => {
    return req.user?.id?.toString() || req.ip;
  },
  // ...
});

The tiered limits

Different user plans can have different limits. The limiters are created once at module load time, not per request, to keep Redis connections stable .

const PLAN_LIMITS = {
  free: { windowMs: 60 * 1000, max: 60 },
  pro: { windowMs: 60 * 1000, max: 600 },
  enterprise: { windowMs: 60 * 1000, max: 6000 },
};

const planLimiters = Object.fromEntries(
  Object.entries(PLAN_LIMITS).map(([plan, config]) => [
    plan,
    rateLimit({ ...config, keyGenerator: (req) => req.user?.id || req.ip }),
  ])
);

function tieredLimiter(req, res, next) {
  const plan = req.user?.plan || 'free';
  return (planLimiters[plan] || planLimiters.free)(req, res, next);
}

d. Input sanitization and validation

Sanitization removes or encodes dangerous characters, while validation checks whether a value is acceptable. Mixing the two carelessly is where subtle bugs creep in. A validation chain that runs but whose result is never checked is worse than no validation, because it gives a false sense of safety .

The validation chain

The express-validator package provides a chain-based API. The handler never executes if validation fails .

const { body, validationResult } = require('express-validator');

app.post(
  '/signup',
  body('email').isEmail().normalizeEmail(),
  body('password').isString().isLength({ min: 12, max: 128 }),
  body('age').optional().isInt({ min: 13, max: 120 }).toInt(),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // req.body is validated and typed
    createUser(req.body);
    res.sendStatus(201);
  }
);

Three habits make this safe: always call validationResult and return early; set explicit bounds (e.g., max: 128 on a password to prevent a multi-megabyte string from reaching the hashing routine); and convert types with toInt() and toBoolean() so downstream code receives real numbers and booleans .

The mass assignment problem

The most common mistake is validating only the fields you expect and passing req.body straight through. If the handler later does Object.assign(user, req.body), an attacker can inject an isAdmin field that was never validated. The fix is to construct the object explicitly rather than spreading the raw body .

// VULNERABLE
Object.assign(user, req.body);

// SECURE
const user = {
  email: req.body.email,
  passwordHash: hash(req.body.password),
};

The XSS sanitization

For HTML content, DOMPurify is the standard sanitizer. It removes script tags and other dangerous markup while preserving safe content .

const createDOMPurify = require('isomorphic-dompurify');
const DOMPurify = createDOMPurify();

const clean = DOMPurify.sanitize(userInput);

For plain text, escaping the HTML special characters is sufficient .

function escapeHtml(text) {
  const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
  return text.replace(/[&<>"']/g, (char) => map[char]);
}

The NoSQL injection prevention

MongoDB queries are vulnerable when user input containing $-prefixed operators is passed directly. The express-mongo-sanitize middleware strips these operators .

const mongoSanitize = require('express-mongo-sanitize');
app.use(mongoSanitize());

The SQL injection prevention

Parameterized queries are the only correct defense. String concatenation is never acceptable .

// BAD
const query = `SELECT * FROM users WHERE id = ${userId}`;

// GOOD
const query = 'SELECT * FROM users WHERE id = ?';
const [rows] = await pool.query(query, [userId]);

Complete Example Session

// ============================================
// PART 1: THE COMPREHENSIVE MIDDLEWARE STACK
// ============================================

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const { body, validationResult } = require('express-validator');

const app = express();

// 1. Security headers
app.use(helmet({
  strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true, preload: true },
  contentSecurityPolicy: { directives: { defaultSrc: ["'self'"] } },
}));

// 2. CORS with explicit allowlist
const allowedOrigins = new Set([
  'https://app.example.com',
  process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : null,
].filter(Boolean));

app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.has(origin)) return callback(null, true);
    return callback(new Error('Origin not allowed by CORS'));
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));

// 3. Rate limiting
const generalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
});
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true,
});

// 4. Body parsing with size limits
app.use(express.json({ limit: '10kb' }));
app.use(express.urlencoded({ extended: true, limit: '10kb' }));

// 5. NoSQL injection prevention
app.use(mongoSanitize());

// ============================================
// PART 2: THE ROUTES
// ============================================

app.use('/api/', generalLimiter);

app.post(
  '/api/auth/login',
  authLimiter,
  body('email').isEmail().normalizeEmail(),
  body('password').isString().isLength({ min: 8, max: 128 }),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    res.json({ ok: true });
  }
);

app.post(
  '/api/users',
  body('email').isEmail().normalizeEmail(),
  body('name').isString().trim().isLength({ min: 2, max: 100 }),
  body('password').isString().isLength({ min: 12, max: 128 }),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // Construct explicitly, never spread req.body
    const user = {
      email: req.body.email,
      name: req.body.name,
      passwordHash: hash(req.body.password),
    };
    res.status(201).json({ message: 'User created' });
  }
);

// ============================================
// PART 3: THE ERROR HANDLER
// ============================================

app.use((err, req, res, next) => {
  // Never expose stack traces
  res.status(500).json({
    error: 'internal_error',
    message: 'An unexpected error occurred',
  });
});

The three parts showed the middleware stack, the routes with per-route validation and rate limits, and the error handler that never exposes internal details.


Quick Reference

Helmet Headers

HeaderDefaultPurpose
Strict-Transport-Securitymax-age=31536000; includeSubDomainsHTTPS preference
X-Content-Type-OptionsnosniffPrevent MIME sniffing
X-Frame-OptionsSAMEORIGINClickjacking protection
Referrer-Policyno-referrerReferrer privacy
Cross-Origin-Resource-Policysame-originResource loading

CORS Options

OptionSafe ValueDangerous Value
originExplicit allowlist'*' or reflect-all
credentialstrue with allowlisttrue with reflect-all
methodsThe methods used'*'
allowedHeadersThe headers used'*'

Rate Limit Configuration

SettingGeneral APIAuth Endpoint
windowMs15 min15 min
max1005
skipSuccessfulRequestsfalsetrue
storeRedis (distributed)Redis
keyGeneratorUser ID or IPUser ID or IP

Validation Rules

FieldRuleReason
Email.isEmail().normalizeEmail()Format and normalization
Password.isLength({ min: 12, max: 128 })Minimum strength and DoS cap
Name.trim().isLength({ min: 2, max: 100 })Whitespace and bounds
Number.isInt().toInt()Type coercion

Sanitization Tools

ThreatTool
XSS (HTML)DOMPurify
XSS (text)escapeHtml
NoSQL injectionexpress-mongo-sanitize
SQL injectionParameterized queries
Mass assignmentExplicit object construction

Best Practices

✅ Do This:

// Use helmet with defaults.
app.use(helmet());  // ✅

// Use an explicit CORS allowlist.
const allowedOrigins = new Set(['https://app.example.com']);  // ✅

// Use a shared rate limit store in distributed deployments.
store: new RedisStore({ client: redisClient });  // ✅

// Set skipSuccessfulRequests on auth limits.
authLimiter = rateLimit({ skipSuccessfulRequests: true });  // ✅

// Validate every field and check the result.
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });  // ✅

// Construct objects explicitly rather than spreading.
const user = { email: req.body.email, name: req.body.name };  // ✅

// Set body size limits.
app.use(express.json({ limit: '10kb' }));  // ✅

❌ Don’t Do This:

// Don't reflect any origin with credentials.
origin: (origin, cb) => cb(null, true), credentials: true  // ❌ Cross-site theft

// Don't use the in-memory rate limit store in production.
rateLimit({ windowMs: 60000, max: 100 })  // ❌ No shared state

// Don't validate without checking the result.
body('email').isEmail()  // ❌ No validationResult call

// Don't spread the raw body.
Object.assign(user, req.body)  // ❌ Mass assignment

// Don't concatenate SQL strings.
`SELECT * FROM users WHERE id = ${id}`  // ❌ Injection

// Don't expose stack traces.
res.status(500).json({ error: err.stack })  // ❌ Information leak

Common Pitfalls

PitfallWhy It HappensFix
CORS reflects any originUsed a function without a checkUse an explicit allowlist
Rate limit ineffective across instancesIn-memory storeUse Redis
Brute force succeedsNo auth-specific limitAdd skipSuccessfulRequests
Mass assignmentSpread req.bodyConstruct explicitly
Validation bypassedForgot validationResultAlways check the result
NoSQL injectionUser input passed to find()Use mongo-sanitize
Oversized payloadNo body limitSet limit: '10kb'
Stack trace leakedError handler returns err.stackReturn a generic message

Real-World Examples

1. Helmet with HSTS

app.use(helmet({
  strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true, preload: true }
}));

2. CORS Allowlist

origin: (origin, cb) => {
  if (!origin || allowedOrigins.has(origin)) return cb(null, true);
  cb(new Error('Not allowed'));
}

3. Auth Rate Limit

const authLimiter = rateLimit({ windowMs: 900000, max: 5, skipSuccessfulRequests: true });

4. Redis Store

store: new RedisStore({ client: redisClient, prefix: 'rl:' })

5. Validation Chain

body('email').isEmail().normalizeEmail(),
body('password').isString().isLength({ min: 12, max: 128 })

6. Check Validation Result

const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });

7. Explicit Object Construction

const user = { email: req.body.email, passwordHash: hash(req.body.password) };

8. NoSQL Sanitization

app.use(mongoSanitize());

9. Body Size Limit

app.use(express.json({ limit: '10kb' }));

10. Generic Error Handler

res.status(500).json({ error: 'internal_error', message: 'An unexpected error occurred' });

Visual

The Request Pipeline

┌────────────────────────────────────────────────────────────┐
│  THE SECURITY PIPELINE                                     │
│                                                            │
│  REQUEST                                                   │
│  ┌──────────────────────────────────────────────────┐     │
│  │  1. HELMET                                        │     │
│  │     Sets security headers.                        │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  2. CORS                                          │     │
│  │     Checks the Origin against the allowlist.      │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  3. RATE LIMIT                                    │     │
│  │     Checks the key against the limit.             │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  4. BODY PARSING + SIZE LIMIT                     │     │
│  │     Rejects oversized payloads.                   │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  5. NO SQL SANITIZATION                           │     │
│  │     Strips $-prefixed operators.                  │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  6. VALIDATION                                    │     │
│  │     Rejects malformed fields.                     │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  7. HANDLER                                       │     │
│  │     Processes the validated request.              │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

The CORS Decision

┌────────────────────────────────────────────────────────────┐
│  CORS DECISION                                             │
│                                                            │
│  Request Origin: https://evil.com                          │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  allowedOrigins = ['https://app.example.com']     │     │
│  │                                                   │     │
│  │  allowedOrigins.has('https://evil.com')           │     │
│  │  → false                                          │     │
│  │                                                   │     │
│  │  callback(new Error('Not allowed'))               │     │
│  │  → The browser blocks the response.               │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  The request is rejected before it reaches the API.        │
└────────────────────────────────────────────────────────────┘

The Rate Limit Tiers

┌────────────────────────────────────────────────────────────┐
│  RATE LIMIT TIERS                                          │
│                                                            │
│  GENERAL API                                               │
│  ┌──────────────────────────────────────────────────┐     │
│  │  100 requests per 15 minutes per user/IP          │     │
│  │  Applies to /api/                                 │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  AUTH ENDPOINTS                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  5 failed attempts per 15 minutes per user/IP     │     │
│  │  Successful logins do not count.                  │     │
│  │  Applies to /api/auth/login, /api/auth/register   │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  The stricter limit on auth endpoints blocks brute force   │
│  without penalizing legitimate users.                      │
└────────────────────────────────────────────────────────────┘

The Validation Boundary

┌────────────────────────────────────────────────────────────┐
│  VALIDATION BOUNDARY                                       │
│                                                            │
│  RAW REQUEST                                               │
│  ┌──────────────────────────────────────────────────┐     │
│  │  { "email": "not-an-email",                       │     │
│  │    "password": "short",                           │     │
│  │    "isAdmin": true }                              │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           │  Validation chain             │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  errors = [                                       │     │
│  │    { field: 'email', msg: 'Invalid email' },      │     │
│  │    { field: 'password', msg: 'Too short' }        │     │
│  │  ]                                                │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           │  400 Bad Request             │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  The handler never executes.                      │     │
│  │  The isAdmin field is never processed.            │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

Summary

ItemValue
HelmetSecurity headers middleware
CORSCross-origin access control
express-rate-limitRate limiting middleware
express-validatorInput validation and sanitization
mongo-sanitizeNoSQL injection prevention
DOMPurifyXSS sanitization for HTML
AllowlistExplicit set of trusted origins
Redis storeShared rate limit state
skipSuccessfulRequestsAuth-specific rate limiting
validationResultThe check that enforces validation

Key takeaways:

  • Helmet sets security headers by default. The app.use(helmet()) call applies a set of safe defaults, and each header can be tuned. Helmet does not validate input or authenticate users; it is one layer among several .
  • CORS must use an explicit allowlist when credentials are involved. The dangerous pattern is a function that reflects any origin while allowing credentials. The safe pattern is an exact-match set of trusted origins .
  • Rate limiting requires a shared store in distributed deployments. The in-memory store tracks only the requests that reach a single instance. Redis ensures every instance decrements the same counter .
  • Auth endpoints need a stricter limit. A general API limit of 100 requests per 15 minutes is too permissive for login. An auth-specific limit of 5 requests with skipSuccessfulRequests: true blocks brute force without penalizing legitimate users .
  • Validation must be checked, not just run. A validation chain that is not followed by validationResult is decorative. The handler must return early if there are errors .
  • Mass assignment is prevented by explicit construction. Never spread req.body into a model. Construct the object field by field, using only the validated values .
  • NoSQL injection is prevented by stripping operators. The express-mongo-sanitize middleware removes $-prefixed keys from user input .
  • SQL injection is prevented by parameterized queries. String concatenation is never acceptable, regardless of how the value is sanitized .
  • Body size limits are a resource protection. The express.json({ limit: '10kb' }) option rejects oversized payloads before they are fully buffered .

Remember: API security is a layered discipline. Helmet closes the header gaps, CORS controls who can read the responses, rate limiting prevents abuse, and validation ensures that hostile payloads are rejected at the boundary. Each layer addresses a specific threat, and none of them is sufficient on its own. The CORS configuration must use an explicit allowlist when credentials are involved; the rate limiter must use a shared store in distributed deployments; the validation chain must be checked; and the handler must construct objects explicitly rather than spreading the raw body. The result is an API that is hardened against the most common classes of attack, and a request pipeline where every layer contributes to the perimeter.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!