@itmcsystemgit/secure-core
v2.0.0
Published
A security toolkit for NestJS backends - AES-256 field encryption, RS256 JWT with a global auth guard, SQL-injection-resistant Prisma access, request validation, rate limiting, and HTTP security defaults.
Readme
@itmcsystemgit/secure-core
A security toolkit for Node.js/NestJS backends, originally built at ITMC Digital. Wire up configuration once per service and every developer gets AES-256 field encryption, RS256 JWT authentication, request validation, SQL-injection protection, rate limiting, and HTTP security hardening — without needing to understand the cryptography underneath.
| | |
|---|---|
| Package | @itmcsystemgit/secure-core |
| Registry | npm — https://www.npmjs.com/package/@itmcsystemgit/secure-core |
| License | Apache-2.0 |
| Runtime | Node.js, NestJS 10.x |
Table of contents
Features
| Feature | What it does |
|---|---|
| Field encryption | AES-256-GCM encryption for individual DB columns, with versioned keys for safe rotation |
| Prisma encryption middleware | Encrypts/decrypts configured model fields automatically on create, update, and updateMany |
| JWT authentication | RS256 signing/verification; a global guard requires a valid token on every route by default |
| Request validation | A hardened ValidationPipe preset that blocks mass-assignment attacks |
| SQL-injection guard | Disables $queryRawUnsafe/$executeRawUnsafe on a Prisma client at runtime |
| Rate limiting | Pre-tuned @nestjs/throttler presets for auth, standard, and public routes |
| HTTP security defaults | helmet response headers and a locked-down-by-default CORS policy |
| Secrets loading | Fetches encryption/JWT keys from AWS Secrets Manager, with in-memory caching |
Requirements
This package expects the following as peer dependencies in the consuming project:
@nestjs/common^10.0.0@nestjs/core^10.0.0class-validator^0.14.0class-transformer^0.5.1
Installation
Published on the public npm registry — no authentication or special configuration needed:
npm install @itmcsystemgit/secure-coreGetting started
1. Generate keys (one time)
Run once, typically by a senior developer or DevOps — not by every engineer:
import { FieldEncryption, SecureJwtService } from '@itmcsystemgit/secure-core';
console.log('Encryption key:', FieldEncryption.generateKey());
console.log('JWT key pair:', SecureJwtService.generateKeyPair());Store the output in AWS Secrets Manager, e.g. under myapp/prod/security-keys:
{
"encryptionKeys": { "v1": "<64 hex chars>" },
"activeKeyVersion": "v1",
"jwtPrivateKey": "-----BEGIN PRIVATE KEY-----...",
"jwtPublicKey": "-----BEGIN PUBLIC KEY-----..."
}Only the auth service needs jwtPrivateKey. Every other service in your system only needs jwtPublicKey to verify tokens — never distribute the private key beyond the auth service.
2. Wire up the module (per service)
app.module.ts:
import { SecureModule, SecretsLoader } from '@itmcsystemgit/secure-core';
@Module({
imports: [
SecureModule.forRootAsync(async () => {
const secrets = await new SecretsLoader().load('myapp/prod/security-keys');
return {
encryptionKeys: secrets.encryptionKeys,
activeEncryptionKeyVersion: secrets.activeKeyVersion,
jwtPublicKey: secrets.jwtPublicKey,
jwtPrivateKey: secrets.jwtPrivateKey, // omit in services that don't issue tokens
jwtIssuer: 'my-auth-service',
};
}),
],
})
export class AppModule {}Importing
SecureModuleprotects every route by default. It registers a global JWT guard — any request without a validAuthorization: Bearer <token>header receives a401automatically, on every controller in the app. Routes that must stay open (login, token refresh, health checks) opt out explicitly:import { Public } from '@itmcsystemgit/secure-core'; @Public() @Post('login') login(@Body() dto: LoginDto) { ... }
3. Apply HTTP security defaults
main.ts:
import { SecureValidationPipe, applySecurityDefaults } from '@itmcsystemgit/secure-core';
app.useGlobalPipes(new SecureValidationPipe());
applySecurityDefaults(app, { corsOrigins: ['https://your-frontend.example.com'] });applySecurityDefaults adds security response headers (via helmet) and locks CORS down to the origins you list — pass nothing and cross-origin requests are rejected entirely until you explicitly allow some.
Usage guide
Read the logged-in user
JWT verification already happened in the global guard — no manual verify() call needed in a handler:
import { CurrentUser } from '@itmcsystemgit/secure-core';
@Get('me')
getProfile(@CurrentUser() user: { userId: string; role: string }) {
return user;
}Only reach for SECURE_JWT_SERVICE directly when verifying a token outside the normal request/response cycle (e.g. a WebSocket handshake, a background job).
Encrypt a field manually
constructor(@Inject(FIELD_ENCRYPTION) private secure: FieldEncryption) {}
const encryptedSalary = this.secure.encrypt('85000');
const salary = this.secure.decrypt(encryptedSalary);Encrypt fields automatically via Prisma (recommended)
In your PrismaService:
import { createEncryptionMiddleware } from '@itmcsystemgit/secure-core';
this.$use(createEncryptionMiddleware(secureInstance, {
Employee: ['salary', 'bankAccountNumber', 'panNumber'],
Driver: ['licenseNumber', 'aadhaarNumber'],
}));After this, prisma.employee.create(...) and .findMany(...) just work — encryption and decryption happen invisibly.
Guard a raw Prisma client against SQL injection
import { forbidUnsafeRawQueries } from '@itmcsystemgit/secure-core';
const prisma = forbidUnsafeRawQueries(new PrismaClient());
// prisma.$queryRawUnsafe(...) / $executeRawUnsafe(...) now throw immediately
// prisma.$queryRaw`...` / $executeRaw`...` (parameterized) still work fineRate-limit a route
import { RateLimitPresets } from '@itmcsystemgit/secure-core';
ThrottlerModule.forRoot(RateLimitPresets.auth)Security checklist
| Don't | Do |
|---|---|
| Use prisma.$queryRawUnsafe / $executeRawUnsafe with string interpolation | Wrap your Prisma client with forbidUnsafeRawQueries() so this is a hard error, not just a convention |
| Put encryption keys or JWT private keys in .env files or commit them | Load them from AWS Secrets Manager via SecretsLoader |
| Write custom crypto.createCipher(...) calls | Use FieldEncryption |
| Store JWTs in localStorage (web) or SharedPreferences (Flutter) | Use httpOnly cookies (web) or flutter_secure_storage (Flutter) |
| — | Mark every new sensitive DB column in the Prisma encryption middleware config |
| — | Use DTOs with class-validator decorators on every endpoint |
Key rotation
When it's time to rotate the AES encryption key:
- Generate a new key:
FieldEncryption.generateKey() - Add it to Secrets Manager as a new version, e.g.
v2, keepingv1 - Set
activeKeyVersion: 'v2'— new writes usev2; old data still decrypts fine viav1(each stored value records which key version encrypted it) - Optionally run a background job to re-encrypt old rows with
v2, then removev1once done
