{"_id":"@ankkho/nestjs-cipher","_rev":"4-89a43df9d96239761a1bfaaf12bd49ad","name":"@ankkho/nestjs-cipher","dist-tags":{"latest":"1.1.4"},"versions":{"1.1.0":{"name":"@ankkho/nestjs-cipher","version":"1.1.0","keywords":["nestjs","encryption","aes-gcm","kms","gcp","aws","azure","pii","data-protection","security","compliance","privacy","gdpr","hipaa","ccpa","nist","cryptography"],"author":"","license":"MIT","_id":"@ankkho/nestjs-cipher@1.1.0","maintainers":[{"name":"ankit_9","email":"ankit.eng.oss@gmail.com"}],"xo":{"rules":{"new-cap":"off","max-params":"off","no-await-in-loop":"off","import/extensions":"off","import-x/extensions":"off","n/no-extraneous-import":"off","n/prefer-global/buffer":"off","@stylistic/curly-newline":"off","n/file-extension-in-import":"off","no-promise-executor-return":"off","@typescript-eslint/ban-ts-comment":"off","@typescript-eslint/no-unsafe-call":"off","import/no-extraneous-dependencies":"off","@typescript-eslint/no-unsafe-return":"off","import-x/no-extraneous-dependencies":"off","@typescript-eslint/naming-convention":"off","@typescript-eslint/no-unsafe-argument":"off","@typescript-eslint/strict-void-return":"off","@typescript-eslint/no-restricted-types":"off","@typescript-eslint/no-unsafe-assignment":"off","@typescript-eslint/no-unsafe-member-access":"off","@typescript-eslint/no-unsafe-type-assertion":"off","@typescript-eslint/switch-exhaustiveness-check":"off"},"space":true,"ignores":["dist","node_modules","example","**/dist/**"],"prettier":true},"dist":{"shasum":"d08f0195174d66b38c3e041ae5d32e999ebc7553","tarball":"https://registry.npmjs.org/@ankkho/nestjs-cipher/-/nestjs-cipher-1.1.0.tgz","fileCount":47,"integrity":"sha512-VX8iRnZaV7JP9lfPjwJ5jrJStOSyPsEfoTbZDKFhM67YbV7QPJttp1JJ1DzLxq94K3rEIaRzvJ41bYeourDXIA==","signatures":[{"sig":"MEQCID+LFT9WYXBeN0K8eh/ztFpBPrG1aQ0TJEp2r8jjQ2iKAiAWX/t0oMbe4Li6c8eGzTRZrih7Gnbbswv1X9n0QuNDJA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":55709},"main":"dist/index.js","_from":"file:ankkho-nestjs-cipher-1.1.0.tgz","types":"dist/index.d.ts","engines":{"node":">=20.0.0"},"scripts":{"lint":"xo --ignore=example","test":"vitest","build":"tsc","example":"node example/dist/example/main.js","release":"changesets publish","test:ui":"vitest --ui","lint:fix":"xo --fix --ignore=example","example:gcp":"node example/dist/example/gcp-adc/gcp-kms.example.js","build:example":"rm -rf example/dist && tsc --project example/tsconfig.json"},"_npmUser":{"name":"ankit_9","email":"ankit.eng.oss@gmail.com"},"_resolved":"/tmp/80d60aa64f596100230820e369f1fdac/ankkho-nestjs-cipher-1.1.0.tgz","_integrity":"sha512-VX8iRnZaV7JP9lfPjwJ5jrJStOSyPsEfoTbZDKFhM67YbV7QPJttp1JJ1DzLxq94K3rEIaRzvJ41bYeourDXIA==","_npmVersion":"10.8.2","description":"Production-grade NestJS encryption module for PII protection with AES-256-GCM and GCP KMS","directories":{},"_nodeVersion":"20.20.2","dependencies":{"@nestjs/core":"^11.1.19","cache-manager":"^7.2.8","@nestjs/common":"^11.1.19","@nestjs/config":"^4.0.4","@nestjs/terminus":"^11.1.1","reflect-metadata":"^0.2.2","@google-cloud/kms":"^5.4.0","@opentelemetry/api":"^1.9.1","@nestjs/cache-manager":"^3.1.2"},"_hasShrinkwrap":false,"devDependencies":{"xo":"^2.0.2","tsx":"^4.21.0","pino":"^9.14.0","husky":"^9.1.7","eslint":"^10.2.1","vitest":"^3.2.4","ts-node":"^10.9.2","prettier":"^3.8.3","typescript":"^5.9.3","@nestjs/cli":"^11.0.21","@types/node":"^25.6.0","nestjs-pino":"^4.6.1","@changesets/cli":"^2.31.0","@commitlint/cli":"^19.8.1","eslint-config-xo":"^0.46.0","eslint-plugin-unicorn":"^63.0.0","eslint-config-prettier":"^10.1.8","@typescript-eslint/parser":"^8.59.1","eslint-config-xo-typescript":"^9.0.0","@commitlint/config-conventional":"^19.8.1","@typescript-eslint/eslint-plugin":"^8.59.1"},"peerDependencies":{"@nestjs/core":"^11.0.0","@nestjs/common":"^11.0.0","@nestjs/config":"^4.0.0","@nestjs/terminus":"^11.0.0","reflect-metadata":"^0.2.0","@google-cloud/kms":"^5.0.0","@opentelemetry/api":"^1.0.0"},"peerDependenciesMeta":{"@nestjs/terminus":{"optional":true},"@google-cloud/kms":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nestjs-cipher_1.1.0_1777636989627_0.05708570802584734","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"@ankkho/nestjs-cipher","version":"1.1.1","keywords":["nestjs","encryption","aes-gcm","kms","gcp","aws","azure","pii","data-protection","security","compliance","privacy","gdpr","hipaa","ccpa","nist","cryptography"],"author":{"name":"Ankit Khosla"},"license":"MIT","_id":"@ankkho/nestjs-cipher@1.1.1","maintainers":[{"name":"ankit_9","email":"ankit.eng.oss@gmail.com"}],"homepage":"https://github.com/ankkho/nestjs-cipher#readme","bugs":{"url":"https://github.com/ankkho/nestjs-cipher/issues"},"xo":{"rules":{"new-cap":"off","max-params":"off","no-await-in-loop":"off","import/extensions":"off","import-x/extensions":"off","n/no-extraneous-import":"off","n/prefer-global/buffer":"off","@stylistic/curly-newline":"off","n/file-extension-in-import":"off","no-promise-executor-return":"off","@typescript-eslint/ban-ts-comment":"off","@typescript-eslint/no-unsafe-call":"off","import/no-extraneous-dependencies":"off","@typescript-eslint/no-unsafe-return":"off","import-x/no-extraneous-dependencies":"off","@typescript-eslint/naming-convention":"off","@typescript-eslint/no-unsafe-argument":"off","@typescript-eslint/strict-void-return":"off","@typescript-eslint/no-restricted-types":"off","@typescript-eslint/no-unsafe-assignment":"off","@typescript-eslint/no-unsafe-member-access":"off","@typescript-eslint/no-unsafe-type-assertion":"off","@typescript-eslint/switch-exhaustiveness-check":"off"},"space":true,"ignores":["dist","node_modules","example","**/dist/**"],"prettier":true},"dist":{"shasum":"f7f650862135eaaeff30d4b57b4def30b6d72b56","tarball":"https://registry.npmjs.org/@ankkho/nestjs-cipher/-/nestjs-cipher-1.1.1.tgz","fileCount":47,"integrity":"sha512-M/lACL027GAJeHatCCGPItV1dOYCV0BvqpF0lo//RRQ1oRZ4KE7i0+rFiyJAGWJ8Gpoo8QX3CT/s6rrJxloZbg==","signatures":[{"sig":"MEYCIQDZan1SaxR3pUyHPWIMe5nL2JZP9Dp7gvQoARiq0IA06QIhAKiD1fCDjAhq9mr7IJH31EcxCuoehgSb1Vl20r22fUJ+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57989},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=22.0.0"},"gitHead":"52f2ade28aa3e7f681e5658a1da75c9aa222db48","scripts":{"lint":"xo --ignore=example","test":"vitest","build":"tsc","example":"node example/dist/example/main.js","prepare":"husky install","release":"changesets publish","test:ui":"vitest --ui","lint:fix":"xo --fix --ignore=example","example:gcp":"node example/dist/example/gcp-adc/gcp-kms.example.js","build:example":"rm -rf example/dist && tsc --project example/tsconfig.json"},"_npmUser":{"name":"ankit_9","email":"ankit.eng.oss@gmail.com"},"repository":{"url":"git+https://github.com/ankkho/nestjs-cipher.git","type":"git"},"_npmVersion":"11.17.0","description":"Production-grade NestJS encryption module for PII protection with AES-256-GCM and KMS","directories":{},"_nodeVersion":"25.6.1","dependencies":{"cache-manager":"^7.2.8","@nestjs/config":"^4.0.4","@nestjs/terminus":"^11.1.1","reflect-metadata":"^0.2.2","@google-cloud/kms":"^5.5.1","@opentelemetry/api":"^1.9.1","@nestjs/cache-manager":"^3.1.3"},"_hasShrinkwrap":false,"packageManager":"pnpm@11.9.0","devDependencies":{"xo":"^2.0.2","tsx":"^4.22.4","pino":"^9.14.0","husky":"^9.1.7","eslint":"^10.6.0","vitest":"^3.2.6","ts-node":"^10.9.2","prettier":"^3.8.5","typescript":"^5.9.3","@nestjs/cli":"^11.0.23","@types/node":"^25.9.4","nestjs-pino":"^4.6.1","@nestjs/core":"^11.1.27","@nestjs/common":"^11.1.27","@changesets/cli":"^2.31.0","@commitlint/cli":"^19.8.1","eslint-config-xo":"^0.51.0","eslint-plugin-unicorn":"^63.0.0","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.27","@typescript-eslint/parser":"^8.62.0","eslint-config-xo-typescript":"^11.0.0","@commitlint/config-conventional":"^20.5.3","@typescript-eslint/eslint-plugin":"^8.62.0"},"peerDependencies":{"@nestjs/core":"^11.0.0","@nestjs/common":"^11.0.0","@nestjs/config":"^4.0.0","@nestjs/terminus":"^11.0.0","reflect-metadata":"^0.2.0","@google-cloud/kms":"^5.0.0","@opentelemetry/api":"^1.0.0"},"peerDependenciesMeta":{"@nestjs/terminus":{"optional":true},"@google-cloud/kms":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nestjs-cipher_1.1.1_1782555935313_0.48854746507509494","host":"s3://npm-registry-packages-npm-production"}},"1.1.3":{"name":"@ankkho/nestjs-cipher","version":"1.1.3","keywords":["nestjs","encryption","aes-gcm","kms","gcp","aws","azure","pii","data-protection","security","compliance","privacy","gdpr","hipaa","ccpa","nist","cryptography"],"author":{"name":"Ankit Khosla"},"license":"MIT","_id":"@ankkho/nestjs-cipher@1.1.3","maintainers":[{"name":"ankit_9","email":"ankit.eng.oss@gmail.com"}],"homepage":"https://github.com/ankkho/nestjs-cipher#readme","bugs":{"url":"https://github.com/ankkho/nestjs-cipher/issues"},"xo":{"rules":{"new-cap":"off","max-params":"off","no-await-in-loop":"off","import/extensions":"off","import-x/extensions":"off","n/no-extraneous-import":"off","n/prefer-global/buffer":"off","@stylistic/curly-newline":"off","n/file-extension-in-import":"off","no-promise-executor-return":"off","@typescript-eslint/ban-ts-comment":"off","@typescript-eslint/no-unsafe-call":"off","import/no-extraneous-dependencies":"off","@typescript-eslint/no-unsafe-return":"off","import-x/no-extraneous-dependencies":"off","@typescript-eslint/naming-convention":"off","@typescript-eslint/no-unsafe-argument":"off","@typescript-eslint/strict-void-return":"off","@typescript-eslint/no-restricted-types":"off","@typescript-eslint/no-unsafe-assignment":"off","@typescript-eslint/no-unsafe-member-access":"off","@typescript-eslint/no-unsafe-type-assertion":"off","@typescript-eslint/switch-exhaustiveness-check":"off"},"space":true,"ignores":["dist","node_modules","example","**/dist/**"],"prettier":true},"dist":{"shasum":"71bdbdbcd1d6cf8ac3e460e1898a1d1269ef43fd","tarball":"https://registry.npmjs.org/@ankkho/nestjs-cipher/-/nestjs-cipher-1.1.3.tgz","fileCount":47,"integrity":"sha512-LidGex7LKb122QYbt/zuh0lsVIulcahGiCCkO/t/IHX3PWP5hR8VNegXT+yM9Xh/YPLnMVCYp8X/iFAK7drkuA==","signatures":[{"sig":"MEYCIQCDN9l/duhRuyHAUGhUpW9GNxeNNR8mzkqi7c7glcMIegIhANikeFJxpW11AAk9BHeWxYOr0Umz/WhPYxGJOSUSQgNO","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ankkho%2fnestjs-cipher@1.1.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":65057},"main":"dist/index.js","_from":"file:ankkho-nestjs-cipher-1.1.3.tgz","types":"dist/index.d.ts","engines":{"node":">=22.0.0"},"scripts":{"lint":"xo --ignore=example","test":"vitest","build":"tsc","example":"node example/dist/example/main.js","release":"changesets publish","test:ui":"vitest --ui","lint:fix":"xo --fix --ignore=example","example:gcp":"node example/dist/example/gcp-adc/gcp-kms.example.js","build:example":"rm -rf example/dist && tsc --project example/tsconfig.json"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4a51a6f1-0426-455f-ae0f-9e8a68b6c469"}},"_resolved":"/home/runner/work/nestjs-cipher/nestjs-cipher/ankkho-nestjs-cipher-1.1.3.tgz","_integrity":"sha512-LidGex7LKb122QYbt/zuh0lsVIulcahGiCCkO/t/IHX3PWP5hR8VNegXT+yM9Xh/YPLnMVCYp8X/iFAK7drkuA==","repository":{"url":"git+https://github.com/ankkho/nestjs-cipher.git","type":"git"},"_npmVersion":"11.16.0","description":"Production-grade NestJS encryption module for PII protection with AES-256-GCM and KMS","directories":{},"_nodeVersion":"24.18.0","dependencies":{"cache-manager":"^7.2.9","@nestjs/config":"^4.0.4","@nestjs/terminus":"^11.1.1","reflect-metadata":"^0.2.2","@google-cloud/kms":"^5.5.1","@opentelemetry/api":"^1.9.1","@nestjs/cache-manager":"^3.1.3"},"_hasShrinkwrap":false,"devDependencies":{"xo":"^2.0.2","tsx":"^4.23.1","pino":"^9.14.0","husky":"^9.1.7","eslint":"^10.6.0","vitest":"^4.1.9","ts-node":"^10.9.2","prettier":"^3.8.5","typescript":"^5.9.3","@nestjs/cli":"^11.0.24","@types/node":"^26.0.1","nestjs-pino":"^4.6.1","@nestjs/core":"^11.1.28","cache-manager":"^7.2.9","@nestjs/common":"^11.1.28","@changesets/cli":"^2.31.1","@commitlint/cli":"^21.2.1","eslint-config-xo":"^0.58.1","eslint-plugin-unicorn":"^69.0.0","eslint-config-prettier":"^10.1.8","@nestjs/platform-express":"^11.1.28","@commitlint/config-conventional":"^20.5.3"},"peerDependencies":{"@nestjs/core":"^11.0.0","@nestjs/common":"^11.0.0","@nestjs/config":"^4.0.0","@nestjs/terminus":"^11.0.0","reflect-metadata":"^0.2.0","@google-cloud/kms":"^5.0.0","@opentelemetry/api":"^1.0.0"},"peerDependenciesMeta":{"@nestjs/terminus":{"optional":true},"@google-cloud/kms":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nestjs-cipher_1.1.3_1785120272095_0.8729779285396935","host":"s3://npm-registry-packages-npm-production"}},"1.1.4":{"name":"@ankkho/nestjs-cipher","version":"1.1.4","description":"Production-grade NestJS encryption module for PII protection with AES-256-GCM and KMS","main":"dist/index.js","types":"dist/index.d.ts","repository":{"type":"git","url":"git+https://github.com/ankkho/nestjs-cipher.git"},"keywords":["nestjs","encryption","aes-gcm","kms","gcp","aws","azure","pii","data-protection","security","compliance","privacy","gdpr","hipaa","ccpa","nist","cryptography"],"author":{"name":"Ankit Khosla"},"license":"MIT","engines":{"node":">=22.0.0"},"dependencies":{"@google-cloud/kms":"^5.5.1","@nestjs/cache-manager":"^3.1.3","@nestjs/config":"^4.0.4","@nestjs/terminus":"^11.1.1","@opentelemetry/api":"^1.9.1","cache-manager":"^7.2.9","reflect-metadata":"^0.2.2"},"peerDependencies":{"@google-cloud/kms":"^5.0.0","@nestjs/common":"^11.0.0","@nestjs/config":"^4.0.0","@nestjs/core":"^11.0.0","@nestjs/terminus":"^11.0.0","@opentelemetry/api":"^1.0.0","reflect-metadata":"^0.2.0"},"peerDependenciesMeta":{"@google-cloud/kms":{"optional":true},"@nestjs/terminus":{"optional":true}},"devDependencies":{"@changesets/cli":"^2.31.1","@commitlint/cli":"^21.2.1","@commitlint/config-conventional":"^20.5.3","@nestjs/cli":"^11.0.24","@nestjs/common":"^11.1.28","@nestjs/core":"^11.1.28","@nestjs/platform-express":"^11.1.28","@types/node":"^26.0.1","cache-manager":"^7.2.9","eslint":"^10.6.0","eslint-config-prettier":"^10.1.8","eslint-config-xo":"^0.58.1","eslint-plugin-unicorn":"^69.0.0","husky":"^9.1.7","nestjs-pino":"^4.6.1","pino":"^9.14.0","prettier":"^3.8.5","ts-node":"^10.9.2","tsx":"^4.23.1","typescript":"^5.9.3","vitest":"^4.1.9","xo":"^2.0.2"},"xo":{"space":true,"prettier":true,"ignores":["dist","node_modules","example","**/dist/**"],"rules":{"import/extensions":"off","import-x/extensions":"off","n/file-extension-in-import":"off","new-cap":"off","import/no-extraneous-dependencies":"off","import-x/no-extraneous-dependencies":"off","n/no-extraneous-import":"off","@typescript-eslint/no-unsafe-return":"off","@typescript-eslint/no-unsafe-call":"off","@typescript-eslint/no-unsafe-assignment":"off","@typescript-eslint/no-unsafe-argument":"off","@typescript-eslint/no-unsafe-member-access":"off","@typescript-eslint/no-unsafe-type-assertion":"off","@typescript-eslint/ban-ts-comment":"off","@typescript-eslint/naming-convention":"off","@typescript-eslint/switch-exhaustiveness-check":"off","@typescript-eslint/no-restricted-types":"off","@typescript-eslint/strict-void-return":"off","@stylistic/curly-newline":"off","n/prefer-global/buffer":"off","no-await-in-loop":"off","no-promise-executor-return":"off","max-params":"off"}},"scripts":{"build":"tsc","build:example":"rm -rf example/dist && tsc --project example/tsconfig.json","example":"node example/dist/example/main.js","example:gcp":"node example/dist/example/gcp-adc/gcp-kms.example.js","lint":"xo --ignore=example","lint:fix":"xo --fix --ignore=example","test":"vitest","test:ui":"vitest --ui","release":"changesets publish"},"_id":"@ankkho/nestjs-cipher@1.1.4","bugs":{"url":"https://github.com/ankkho/nestjs-cipher/issues"},"homepage":"https://github.com/ankkho/nestjs-cipher#readme","_integrity":"sha512-99JGqoqmMCHZAtaol4h0NAL/ItEceVClfmV4fv79RuLYIvABt4CZT9riVADZ/TkY/dkE/WraFy1FL3X7h5/dqw==","_resolved":"/home/runner/work/nestjs-cipher/nestjs-cipher/ankkho-nestjs-cipher-1.1.4.tgz","_from":"file:ankkho-nestjs-cipher-1.1.4.tgz","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-99JGqoqmMCHZAtaol4h0NAL/ItEceVClfmV4fv79RuLYIvABt4CZT9riVADZ/TkY/dkE/WraFy1FL3X7h5/dqw==","shasum":"00f936c6552f9324c414741c732dcb51522c0002","tarball":"https://registry.npmjs.org/@ankkho/nestjs-cipher/-/nestjs-cipher-1.1.4.tgz","fileCount":47,"unpackedSize":65057,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ankkho%2fnestjs-cipher@1.1.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDg7O935BUh5i+pHKBI8zf71vV31jpe5r7en8mU+Vb8OQIhAP77zc6VuTxyiyfhikqY3kZKujBmv8ONxrRX9wKlUqvS"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:4a51a6f1-0426-455f-ae0f-9e8a68b6c469"}},"directories":{},"maintainers":[{"name":"ankit_9","email":"ankit.eng.oss@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nestjs-cipher_1.1.4_1785120424541_0.4445242661412361"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-01T12:03:09.538Z","modified":"2026-07-27T02:47:05.094Z","1.1.0":"2026-05-01T12:03:09.775Z","1.1.1":"2026-06-27T10:25:35.489Z","1.1.3":"2026-07-27T02:44:32.246Z","1.1.4":"2026-07-27T02:47:04.698Z"},"bugs":{"url":"https://github.com/ankkho/nestjs-cipher/issues"},"author":{"name":"Ankit Khosla"},"license":"MIT","homepage":"https://github.com/ankkho/nestjs-cipher#readme","keywords":["nestjs","encryption","aes-gcm","kms","gcp","aws","azure","pii","data-protection","security","compliance","privacy","gdpr","hipaa","ccpa","nist","cryptography"],"repository":{"type":"git","url":"git+https://github.com/ankkho/nestjs-cipher.git"},"description":"Production-grade NestJS encryption module for PII protection with AES-256-GCM and KMS","maintainers":[{"name":"ankit_9","email":"ankit.eng.oss@gmail.com"}],"readme":"# nestjs-cipher\n\n![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue) ![NestJS](https://img.shields.io/badge/NestJS-11-red) ![Node.js](https://img.shields.io/badge/Node.js-22+-green) ![pnpm](https://img.shields.io/badge/pnpm-11.0+-F69D3D) ![License](https://img.shields.io/badge/License-MIT-yellow)\n\n> Production-grade NestJS encryption module. Protect sensitive data (emails, PII, tokens) with **AES-256-GCM** + **Google Cloud KMS**. Local mode for development.\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Configuration](#configuration)\n- [Architecture](#architecture)\n- [Multi-Tenant](#multi-tenant)\n- [Infrastructure](#infrastructure-opentofu)\n- [Observability](#observability)\n- [Security](#security)\n- [Troubleshooting](#troubleshooting)\n- [Development](#development)\n- [Resources](#resources)\n\n## Quick Start\n\n### 1. Install\n\n```bash\npnpm install @ankkho/nestjs-cipher\n```\n\n**Requirements:** Node.js ≥ 22, pnpm ≥ 11\n\n### 2. Configure\n\n```typescript\nimport { ConfigModule, ConfigService } from '@nestjs/config';\nimport { CipherModule, Providers } from '@ankkho/nestjs-cipher';\n\n@Module({\n  imports: [\n    ConfigModule.forRoot(),\n    CipherModule.forRootAsync({\n      imports: [ConfigModule],\n      inject: [ConfigService],\n      useFactory: (config: ConfigService) => ({\n        provider: Providers.GCP_KMS,\n        gcp: {\n          projectId: config.getOrThrow('GCP_PROJECT_ID'),\n          keyRing: config.getOrThrow('GCP_KMS_KEY_RING'),\n          location: config.getOrThrow('GCP_KMS_LOCATION'),\n        },\n      }),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n```bash\n# .env\nGCP_PROJECT_ID=my-project\nGCP_KMS_LOCATION=us-central1\nGCP_KMS_KEY_RING=pii-ring\n```\n\n### 3. Use\n\n```typescript\nimport { CipherService } from '@ankkho/nestjs-cipher';\n\n@Injectable()\nexport class UserService {\n  constructor(private cipher: CipherService) {}\n\n  async createUser(email: string, tenantId: string) {\n    const encrypted = await this.cipher.encrypt(email, { tenantId });\n    await db.users.create({ email_encrypted: encrypted });\n  }\n\n  async getUser(userId: string, tenantId: string) {\n    const stored = await db.users.findOne(userId);\n    const email = await this.cipher.decrypt(stored.email_encrypted, {\n      tenantId,\n    });\n    return { ...stored, email };\n  }\n}\n```\n\nThat's it. New tenant keys are created automatically on first encrypt — no manual provisioning needed.\n\n### Local Development\n\n```typescript\nimport { CipherModule, Providers } from '@ankkho/nestjs-cipher';\n\n@Module({\n  imports: [CipherModule.forRoot({ provider: Providers.LOCAL })],\n})\nexport class AppModule {}\n```\n\nIn-memory keys only. Not for production.\n\n## Configuration\n\n### GCP KMS\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `GCP_PROJECT_ID` | Yes | GCP project ID |\n| `GCP_KMS_KEY_RING` | Yes | KMS key ring name |\n| `GCP_KMS_LOCATION` | Yes | Key ring location (`us-central1`, `global`, etc.) |\n\n**Credentials:** Uses [Application Default Credentials (ADC)](https://cloud.google.com/docs/authentication/application-default-credentials).\n\n```bash\n# Option 1: Service account key\nexport GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json\n\n# Option 2: gcloud (development only)\ngcloud auth application-default login\n```\n\n**IAM:** Service account needs `roles/cloudkms.cryptoKeyAdmin` on the key ring. This role allows the library to create keys automatically for new tenants.\n\nIf you prefer to pre-provision all keys via Tofu/gcloud and restrict the service account to encryption-only, use `roles/cloudkms.cryptographer` instead. New tenant keys will not be created automatically.\n\n### Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `GCP_PROJECT_ID` | — | GCP project ID |\n| `GCP_KMS_KEY_RING` | — | KMS key ring name |\n| `GCP_KMS_LOCATION` | — | Key ring location |\n\n## Architecture\n\n### Envelope Encryption\n\n```\nplaintext ──► AES-256-GCM (local) ──► ciphertext\n                 │\n                 ├──► random 32-byte DEK (data encryption key)\n                 │         │\n                 │         ▼\n                 │    KMS encrypt (wrap DEK) ──► wrapped DEK\n                 │\n                 ▼\n           { v, ciphertext, wrappedDek, iv, tag }\n```\n\n1. Generate a random 32-byte DEK\n2. Encrypt plaintext locally with AES-256-GCM\n3. Wrap the DEK with KMS (one API call)\n4. Zero the DEK from memory\n5. Return the encrypted payload\n\n**Why envelope encryption?** Local AES-256-GCM is fast (~1ms). KMS wrapping adds security without encrypting every field through the network. The DEK is never persisted — it's generated, used, and zeroed each operation.\n\n### Encrypted Payload\n\n```json\n{\n  \"v\": 1,\n  \"ciphertext\": \"...\",\n  \"wrappedDek\": \"...\",\n  \"iv\": \"...\",\n  \"tag\": \"...\"\n}\n```\n\n| Field | Purpose |\n|-------|---------|\n| `v` | Payload version (for future algorithm migrations) |\n| `ciphertext` | AES-256-GCM encrypted data |\n| `wrappedDek` | DEK encrypted by KMS (safe to store) |\n| `iv` | Initialization vector |\n| `tag` | GCM authentication tag |\n\n**Requirements:**\n- At least one of `tenantId` or `userId` is required\n- Use the same context for encrypt and decrypt\n- Store the full payload — all fields are needed for decryption\n\n### Key Auto-Creation\n\nThe library automatically creates KMS keys for new tenants on first encrypt. If `tenant-{tenantId}` doesn't exist, it's created with:\n\n- Purpose: `ENCRYPT_DECRYPT`\n- Algorithm: `GOOGLE_SYMMETRIC_ENCRYPTION`\n- Protection: `SOFTWARE`\n- Rotation: 90 days\n\nExisting keys are reused — no-op on subsequent calls.\n\n## Multi-Tenant\n\nEach tenant gets an isolated KMS key:\n\n```\ntenantId: \"org-100\"  → .../cryptoKeys/tenant-org-100\ntenantId: \"org-200\"  → .../cryptoKeys/tenant-org-200\nuserId:   \"usr-42\"   → .../cryptoKeys/user-usr-42\n```\n\nTenant A cannot decrypt Tenant B's data. Key isolation is enforced by KMS.\n\n### Single-Tenant\n\nUse a consistent `tenantId` (e.g., `\"default\"`) across all encrypt/decrypt calls:\n\n```typescript\nawait this.cipher.encrypt(email, { tenantId: 'default' });\nawait this.cipher.decrypt(encrypted, { tenantId: 'default' });\n```\n\n### Multi-Tenant (SaaS)\n\nPass the tenant's ID from your auth context:\n\n```typescript\nawait this.cipher.encrypt(email, { tenantId: tenant.id });\nawait this.cipher.decrypt(encrypted, { tenantId: tenant.id });\n```\n\nKeys are created automatically on first encrypt. No infrastructure changes needed per tenant.\n\n### User-Level Isolation\n\nFor per-user key isolation (e.g., end-to-end encryption):\n\n```typescript\nawait this.cipher.encrypt(message, { userId: user.id });\n```\n\n## Infrastructure (OpenTofu)\n\nPre-provision known tenants with Tofu. Unknown tenants are auto-created at runtime by the library.\n\n### Quick Setup\n\nCopy `kms.tf` into your existing Tofu module. It assumes your module already has `var.project_id`, `var.environment`, and `var.location`.\n\n```hcl\n# In your .tfvars\ntenant_names = [\"org-acme\", \"org-globex\"]  # optional, defaults to [\"default\"]\n```\n\n```bash\ntofu plan\ntofu apply\n```\n\n### SaaS: Runtime Key Creation\n\nFor apps where tenants are created at runtime, grant `cryptoKeyAdmin` so the library can create keys:\n\n```hcl\n# In your .tfvars\nkms_service_account_emails = [\"cipher@my-project.iam.gserviceaccount.com\"]\n```\n\n### IAM Roles\n\n| Role | When to Use |\n|------|-------------|\n| `roles/cloudkms.cryptoKeyAdmin` | Library creates keys automatically (SaaS, dynamic tenants) |\n| `roles/cloudkms.cryptographer` | All keys pre-provisioned via Tofu/gcloud (fixed tenants) |\n\nSee [`infra/tofu/gcp/README.md`](./infra/tofu/gcp/README.md) for full Tofu documentation.\n\n## Observability\n\nOpenTelemetry spans are created automatically:\n\n| Span | Description |\n|------|-------------|\n| `nestjs-cipher.encrypt` | Encrypt operation |\n| `nestjs-cipher.decrypt` | Decrypt operation |\n\n**Attributes:**\n- `cipher.provider` — KMS provider (e.g., `GCP_KMS`)\n- `cipher.context.type` — `tenant` or `user`\n- `cipher.payload.version` — Payload version\n\nSetup is automatic if OTel SDK is configured in your NestJS app.\n\n## Security\n\n### Best Practices\n\n1. Store credentials in a secure vault (GCP Secret Manager, HashiCorp Vault). Never commit keys.\n2. Use `roles/cloudkms.cryptoKeyAdmin` only on the key ring — not at project level.\n3. Enable automatic key rotation (90 days recommended, configured by default).\n4. Monitor Cloud Audit Logs for unauthorized KMS access.\n5. Use TLS for all network communication.\n\n### Data Isolation\n\n- Each tenant/user gets a distinct KMS key\n- DEKs are generated per-operation and zeroed from memory after use\n- Unwrapped DEKs are cached for 5 minutes (configurable) then discarded\n- The library never persists plaintext keys\n\n### Key Lifecycle\n\n| Stage | What Happens |\n|-------|-------------|\n| First encrypt for new tenant | KMS key created automatically |\n| Subsequent encrypts | Existing key reused |\n| Key rotation (90d) | New key version created, old versions remain for decryption |\n| Tenant deleted | KMS key soft-deleted (30-day recovery window) |\n\n## Troubleshooting\n\n| Issue | Solution |\n|-------|----------|\n| `Module fails at startup` | Verify ADC credentials are set and valid |\n| `Decryption fails` | Ensure same `tenantId`/`userId` used for encrypt and decrypt |\n| `PERMISSION_DENIED on key creation` | Grant `roles/cloudkms.cryptoKeyAdmin` on the key ring |\n| `NOT_FOUND` on encrypt | Key doesn't exist and auto-creation is disabled. Check IAM permissions. |\n| High latency | Check network to GCP. DEK caching reduces KMS calls (5 min TTL). |\n| `GOOGLE_APPLICATION_CREDENTIALS not set` | Set the env var or run `gcloud auth application-default login` |\n\n## Development\n\n```bash\npnpm install          # Install dependencies\npnpm build            # Build library\npnpm test             # Run tests\npnpm lint:fix         # Lint and format\nnpx tsc --noEmit      # Type check\n```\n\n### Running Examples\n\n```bash\n# Local mode\npnpm build:example && pnpm example\n\n# GCP KMS\ncd example/tofu-gcp && source .env.gcp && pnpm build && pnpm example:gcp\n```\n\n## Resources\n\n- [Contributing](https://github.com/ankkho/nestjs-cipher/blob/main/CONTRIBUTING.md)\n- [Release Process](https://github.com/ankkho/nestjs-cipher/blob/main/RELEASE_README.md)\n- [Security Policy](https://github.com/ankkho/nestjs-cipher/blob/main/.github/SECURITY.md)\n- [Infrastructure (OpenTofu)](https://github.com/ankkho/nestjs-cipher/blob/main/infra/tofu/gcp/README.md)\n- [Example](https://github.com/ankkho/nestjs-cipher/tree/main/example)\n\n## License\n\nMIT\n","readmeFilename":"README.md"}