Actionable, security-focused rules for building and maintaining hardened Node.js/Express back-end services.
Your Node.js applications are under constant attack. Every unvalidated input, every exposed header, every unpatched dependency is a potential entry point for attackers. While most developers focus on functionality first and security as an afterthought, these Cursor Rules flip that approach entirely.
Node.js backends face unique security challenges that generic coding standards don't address:
package.json contains packages with known CVEs that automated scanners exploit dailyMost security guides give you theoretical advice. You need actionable, implementable rules that integrate directly into your development workflow.
These Cursor Rules implement defense-in-depth security patterns proven in production environments handling millions of requests. They enforce security-first coding practices that prevent vulnerabilities before they reach your codebase.
Core Security Enforcement:
Real-Time Vulnerability Prevention:
npm audit and Snyk for dependency scanningEliminate Security Debt Before Code Review Your security validation happens at write-time, not during expensive security audits. The rules enforce parameterized queries, input validation, and proper error handling as you type.
// Before: Vulnerable to SQL injection
const user = await db.raw(`SELECT * FROM users WHERE id = ${userId}`);
// After: Automatically enforced parameterized query
const user = await knex<User>('users').where({ id: userId }).first();
Reduce Security Review Cycles by 70% Security-compliant code patterns become automatic. Your pull requests pass security reviews faster because common vulnerabilities are prevented at the source.
Stop Production Security Incidents Proper error masking, input validation, and authentication patterns prevent the security issues that cause 3 AM incident calls.
Instead of implementing JWT authentication from scratch and missing security details:
// Cursor Rules automatically generate secure JWT middleware
export const authenticateJWT = (req: Request, res: Response, next: NextFunction) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return next(createError(401, 'Access token required'));
}
try {
const decoded = jwt.verify(token, process.env.JWT_PUBLIC_KEY!, {
algorithms: ['RS256'],
issuer: process.env.JWT_ISSUER,
audience: process.env.JWT_AUDIENCE,
});
req.user = decoded;
next();
} catch (error) {
next(createError(401, 'Invalid token'));
}
};
Your API routes automatically include schema validation that prevents malicious payloads:
// Validation middleware generated with proper error handling
export const validateCreateUser = validateBody(z.object({
email: z.string().email().max(255),
password: z.string().min(8).max(128),
role: z.enum(['user', 'admin']).default('user')
}));
app.post('/users', validateCreateUser, async (req, res, next) => {
// req.body is now type-safe and validated
const user = await userService.create(req.body);
res.json({ user });
});
Every database query follows parameterized patterns that eliminate injection risks:
// Sequelize queries with proper parameterization
const users = await User.findAll({
where: {
department: req.query.dept,
active: true
},
attributes: ['id', 'name', 'email'] // No sensitive fields
});
Copy the rules configuration into your .cursorrules file in your project root. The rules integrate with your existing Node.js/Express setup without requiring architecture changes.
Add the required security packages to your project:
npm install helmet cors express-rate-limit bcrypt zod http-errors
npm install --save-dev eslint-plugin-security @types/bcrypt
The rules enforce a specific middleware order that maximizes security:
// Generated secure Express setup
app.disable('x-powered-by');
// Security middleware in correct order
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
},
},
}));
app.use(express.json({ limit: '1mb' }));
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(','),
credentials: true
}));
app.use(rateLimiter);
Add security scanning to your GitHub Actions workflow:
- name: Run security audit
run: |
npm audit --production --audit-level=high
npx eslint . --ext .js,.ts --config .eslintrc.security.js
- name: Snyk vulnerability scan
run: npx snyk test --severity-threshold=high
The rules automatically adjust security strictness based on your environment:
// Development vs Production security configuration
const securityConfig = {
development: {
logLevel: 'debug',
maskErrors: false,
rateLimitWindow: 60000,
},
production: {
logLevel: 'info',
maskErrors: true,
rateLimitWindow: 900000,
}
};
Security Posture Improvements:
Development Velocity Gains:
Production Reliability:
Compliance & Audit Benefits:
These rules transform your Node.js development from reactive security patching to proactive vulnerability prevention. Your applications become secure by default, your security reviews become faster, and your production incidents become rare.
The difference between hoping your code is secure and knowing it follows proven security patterns - that's what these Cursor Rules deliver.
# You are an expert in Node.js, Express, JavaScript (ES2023), TypeScript, OAuth 2.0, JWT, HTTPS, Helmet.js, bcrypt, Sequelize/Knex/Mongoose, Snyk, npm-audit, OpenTelemetry, Datadog, eslint-plugin-security, rate-limiter-flexible, Docker, Kubernetes, GitHub Actions.
---
## Key Principles
- Security-first mindset: treat every line of code as an attack surface.
- Default-deny: block everything, explicitly allow only what is needed (Principle of Least Privilege).
- Fail secure: on error, deny access and log; never expose internal details.
- Immutable infrastructure: redeploy, don’t patch in place.
- Automate: CI runs linting, tests, vuln scans, secret scans on every push.
- Defence in depth: combine TLS, authn, authz, input validation, rate limiting, monitoring.
---
## JavaScript / TypeScript Rules (Node.js Runtime)
- Always `"use strict";` (or TypeScript) at top-level.
- Use **ES modules** (`import`/`export`) OR **TypeScript** with `"module": "esnext"`.
- Prefer **async/await**; never mix with `.then()` chains in the same scope.
- Never disable linter security rules (`eslint-plugin-security`, `@typescript-eslint/no-unsafe-*`).
- Disallow `eval`, `Function`, `child_process.exec` unless absolutely required and sandboxed.
- Use **const** for values that never reassign, **let** otherwise; never use `var`.
- Store secrets in environment variables or a secret manager; never commit to VCS.
- Sanitize all external input with a whitelist schema (e.g., `zod`, `joi`).
- Use Parameterized queries or ORM query builders (Sequelize/Knex/Mongoose) – never string-concatenate SQL or Mongo queries.
- Require HTTPS URLs (`process.env.NODE_ENV!=='development'`) before making outbound requests.
Example – secure parameterized query with Knex (TS):
```ts
const user = await knex<User>('users')
.where({ id })
.first();
```
---
## Error Handling and Validation
- Centralise errors in `src/middleware/errorHandler.ts`:
- Mask stack traces in production: return generic messages (`"Something went wrong"`).
- Log full error + request id to APM (Datadog/New Relic).
- Use `http-errors` package to create typed HTTP errors.
- Validate **before** reaching business logic; reject early.
- Example middleware:
```ts
import createError from 'http-errors';
export const validateBody = (schema: Schema): RequestHandler => (req, _res, next) => {
const parsed = schema.safeParse(req.body);
if (!parsed.success) return next(createError(400, 'Invalid payload'));
req.body = parsed.data;
next();
};
```
---
## Express Framework Rules
- `app.disable('x-powered-by');` to hide Express version.
- Global middleware order:
1. `helmet()` – enable all default headers; explicitly set `contentSecurityPolicy`.
2. `express.json({ limit: '1mb' })` – restrict body size.
3. `cors({ origin: ALLOWLIST, credentials: true })` – strict origins.
4. `rateLimiter` (rate-limiter-flexible) – 100 req/15 min default.
5. `sanitize()` – input sanitization.
6. Authentication (`passport-jwt`, `express-jwt`, or OAuth 2.0 middleware).
7. RBAC authorization (`casbin`, custom middleware).
- Never mount unfiltered `static()` before auth middleware.
- For JWT:
- Use RS256 asymmetric keys; rotate keys via JWKS.
- Set `exp` <= 15 m and `nbf`, `iat` claims.
- Store only in `Authorization: Bearer` header; never in cookies (unless HttpOnly+SameSite=strict).
- Use CLS/AsyncLocalStorage to attach `requestId` to logs.
---
## Additional Sections
### Testing
- Write unit tests for every validator & auth guard (`jest --coverage`).
- Run **OWASP ZAP** & **npm run test:security** nightly.
- Include `supertest` to assert 4xx/5xx logic doesn’t leak secrets.
### Dependency Management
- `npm audit --production --audit-level=high` in CI.
- `npx npm-check-updates -u && npm install` weekly via Dependabot.
- Block build if Snyk reports CVSS ≥ 7 unless explicitly waived.
### Performance & DoS Hardening
- Avoid long synchronous loops; offload to worker threads.
- Set `server.keepAliveTimeout` ≤ 10 s to free sockets.
- Use `cluster` or k8s horizontal pod autoscaling.
### Secrets & Configuration
- Use `.env.example` template; load with `@dotenv-safe` validation.
- In Kubernetes, mount secrets as tmpfs volumes with `readOnly: true`.
- Rotate DB credentials every 90 days.
### Monitoring & Observability
- Instrument with **OpenTelemetry SDK**; propagate `traceparent` header.
- Alert on ≥ 5 xx error rate spikes, auth failures, excessive 429s.
### Logging
- Use pino/winston in JSON mode (`level: 'info'` in prod).
- Redact sensitive fields (`password`, `Authorization`, `set-cookie`).
### Containerization
- Use `node:18-alpine` minimal images.
- Multi-stage build: dependencies in build stage, only `node_modules --production` in runtime.
- Run as non-root UID 1001; drop all Linux capabilities except `NET_BIND_SERVICE`.
---
### Common Pitfalls
- Forgetting to await async validation → unhandled promise rejection.
- Leaving default configuration in Helmet (CSP disabled).
- Exposing `/graphql` introspection in production – disable (`introspection: false`).
- Passing JWT secrets via command line (shows in `ps` output).
---
### File/Folder Conventions
```
src/
config/ # config, env schemas
middleware/ # auth, validation, errorHandler
routes/ # route modules
controllers/ # business logic
models/ # ORM models
services/ # external integrations
utils/ # helpers (pure functions only)
tests/ # jest
```