titan-vault-node 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,66 +1,159 @@
1
- # 🛡️ Titan Vault (Node.js SDK)
1
+ # 🛡️ Titan Vault (Node.js SDK) — Modo Paranoia & Zero-Trust
2
2
 
3
- > **Zero-trust, E2E encrypted, drop-in replacement for dotenv.**
3
+ > **SDK de seguridad Zero-Trust, Criptografía E2E, Envenenamiento de `process.env` y Protección de Memoria RAM.**
4
4
 
5
- Titan Vault is the easiest way to secure your application secrets. Forget about hardcoded `.env` files that get leaked. Titan Vault acts as a highly secure, encrypted environment variable injector that requires **zero friction** to use.
5
+ `titan-vault-node` resuelve la vulnerabilidad fundamental de Node.js: **la exposición de secretos en texto plano en la memoria RAM (Heap Dumps) y `process.env`.**
6
6
 
7
- ## 🚀 The Problem with `.env`
8
- If a hacker gets read access to your server (LFI, directory traversal, etc.), they can read your `.env` file and steal all your database passwords and API keys.
7
+ ---
8
+
9
+ ## ⚡ ¿Por qué `dotenv` y `process.env` son inseguros?
10
+
11
+ 1. **Memory Dumps:** Cualquier vulnerabilidad que permita un volcado de memoria (heapdump, coredump, inspector v8) expone todas las variables de entorno en texto plano.
12
+ 2. **Inmutabilidad V8:** En Node.js, `process.env` almacena strings inmutables. El recolector de basura (GC) no puede sobreescribir con ceros esas cadenas en memoria.
13
+ 3. **Peligro de Logs:** Un simple `console.log(process.env)` o reporte de error APM filtra todas tus credenciales.
14
+
15
+ ---
16
+
17
+ ## 🛡️ La Solución: Modo Paranoia (Los 4 Pilares)
18
+
19
+ `titan-vault-node` implementa una arquitectura de grado militar:
20
+
21
+ 1. **Patrón Bootstrap (Arranque Seguro):** Pausa el arranque del runtime para negociar llaves efímeras (RSA-2048 + AES-256-CFB) y proteger la memoria antes de evaluar el código de negocio.
22
+ 2. **Envenenamiento de `process.env`:** Borra los secretos del entorno global y coloca un `Proxy` reactivo. Si alguien o una librería de terceros intenta hacer `process.env.JWT_SECRET`, la ejecución se interrumpe con `ParanoiaBlockedError`. Además, las variables secretas no son enumerables (no aparecen en `Object.keys()` ni `console.log(process.env)`).
23
+ 3. **Criptografía Nativa C++ (`KeyObject`):** Las llaves simétricas (JWT, cookies, AES) se cargan directamente como instancias nativas `crypto.KeyObject` de C++, invisibles a búsquedas de strings en el heap de V8.
24
+ 4. **Inyección JIT en Bases de Datos:**
25
+ - **Sequelize:** Gancho `beforeConnect` para inyectar contraseñas microsegundos antes del handshake, y `afterConnect` para nulificar (`password = null`) de inmediato.
26
+ - **node-postgres (`pg`):** Función asíncrona de contraseña bajo demanda (`async () => password`).
27
+ 5. **CaaS / Transit Engine:** Criptografía como Servicio. Permite firmar tokens o cifrar datos directamente en el servidor Titan Vault sin mantener ninguna llave en memoria.
28
+
29
+ ---
30
+
31
+ ## 💻 Guía de Integración
9
32
 
10
- ## 💡 The Titan Vault Solution
11
- Titan Vault stores your secrets in a centralized, encrypted server. Your app authenticates using a lightweight AppToken, and the Vault dynamically injects the secrets directly into Node.js memory (`process.env`) using **Hybrid E2E Encryption (RSA-OAEP + AES-CFB)**. If the app stops, the secrets disappear.
33
+ ### 1. El Patrón Bootstrap (`bootstrap.js` o `src/index.ts`)
12
34
 
13
- ### 🔥 Killer Features
14
- - **Zero-Friction SDK:** Replaces `dotenv` with exactly 2 lines of code.
15
- - **E2E Encryption out-of-the-box:** Secrets are never sent in plain text over the network.
16
- - **Honeytokens & Webhooks:** Create fake "Honeytoken" secrets. If a hacker tries to use them, the Vault instantly locks down and sends an alert to your **Telegram/Slack**.
17
- - **Burn-on-read Tokens:** For maximum paranoia, use one-time wrap tokens that self-destruct after your app boots.
35
+ Crea un archivo de arranque que cargue el SDK antes de importar el servidor:
18
36
 
19
- ## 📦 Installation
37
+ ```typescript
38
+ // bootstrap.ts
39
+ import { TitanVault } from 'titan-vault-node';
40
+
41
+ async function main() {
42
+ // 1. Arma el Modo Paranoia y asegura la memoria
43
+ await TitanVault.armParanoiaMode({
44
+ vaultUrl: process.env.VAULT_URL || 'http://127.0.0.1:8200',
45
+ vaultToken: process.env.VAULT_APP_TOKEN,
46
+ path: 'mi_aplicacion_env',
47
+ });
48
+
49
+ // 2. Importa el servidor dinámicamente UNA VEZ que la memoria está blindada
50
+ await import('./server.js');
51
+ }
20
52
 
21
- ```bash
22
- npm install titan-vault-node
53
+ main();
23
54
  ```
24
55
 
25
- ## 💻 Usage
56
+ ---
57
+
58
+ ### 2. Firma y Validación de Tokens JWT (C++ `KeyObject`)
59
+
60
+ Compatible de forma nativa con `jsonwebtoken`, `jose` y el módulo `crypto` de Node.js:
26
61
 
27
- Replace your standard `dotenv.config()` with `initVault()`.
62
+ ```typescript
63
+ import jwt from 'jsonwebtoken';
64
+ import { TitanVault } from 'titan-vault-node';
28
65
 
29
- ### 1. Set your App Token
30
- In your deployment environment (or a local `.env`), just provide your vault's URL and your App Token:
66
+ // Obtiene el KeyObject de C++ (Invisible en volcados de RAM)
67
+ const jwtKey = TitanVault.secrets.get('JWT_SECRET');
31
68
 
32
- ```env
33
- VAULT_URL=http://localhost:8200
34
- VAULT_APP_TOKEN=my-super-secret-app-role-token
69
+ // Firmar
70
+ const token = jwt.sign({ userId: 123, role: 'admin' }, jwtKey, {
71
+ algorithm: 'HS256',
72
+ expiresIn: '2h',
73
+ });
74
+
75
+ // Verificar
76
+ const payload = jwt.verify(token, jwtKey);
35
77
  ```
36
78
 
37
- ### 2. Initialize in your code
79
+ ---
80
+
81
+ ### 3. Conexión Segura a Base de Datos (JIT)
38
82
 
83
+ #### Con Sequelize:
39
84
  ```typescript
40
- import { initVault } from 'titan-vault-node';
85
+ import { Sequelize } from 'sequelize';
86
+ import { TitanVault } from 'titan-vault-node';
87
+
88
+ export const sequelize = new Sequelize({
89
+ dialect: 'postgres',
90
+ host: TitanVault.config.get('DB_HOST'),
91
+ database: TitanVault.config.get('DB_NAME'),
92
+ username: TitanVault.config.get('DB_USER'),
93
+ // No pongas password aquí
94
+ });
95
+
96
+ // Registra el inyector JIT
97
+ TitanVault.databases.hookSequelize(sequelize, {
98
+ credentialKey: 'DB_PASSWORD',
99
+ });
100
+ ```
41
101
 
42
- async function startServer() {
43
- // Magically connects to Titan Vault, decrypts the E2E payload,
44
- // and injects everything into process.env!
45
- await initVault();
102
+ #### Con `pg` (node-postgres):
103
+ ```typescript
104
+ import pg from 'pg';
105
+ import { TitanVault } from 'titan-vault-node';
106
+
107
+ export const pool = new pg.Pool({
108
+ host: TitanVault.config.get('DB_HOST'),
109
+ user: TitanVault.config.get('DB_USER'),
110
+ database: TitanVault.config.get('DB_NAME'),
111
+ // Inyección JIT asíncrona por conexión
112
+ password: TitanVault.databases.getPgPasswordProvider('DB_PASSWORD'),
113
+ });
114
+ ```
46
115
 
47
- console.log("My secure DB password is:", process.env.DB_PASSWORD);
48
-
49
- // Start your Express/NestJS/HTTP server...
50
- }
116
+ ---
51
117
 
52
- startServer();
118
+ ### 4. Criptografía como Servicio (Transit Engine / CaaS)
119
+
120
+ Para máxima seguridad, puedes delegar el cifrado y la firma directamente a Titan Vault (cero llaves en tu app):
121
+
122
+ ```typescript
123
+ import { TitanVault } from 'titan-vault-node';
124
+
125
+ // Cifrar datos con AES-256-GCM en el Vault
126
+ const ciphertext = await TitanVault.transit.encrypt('mi-llave', 'Texto Ultra Confidencial');
127
+
128
+ // Descifrar en el Vault
129
+ const plaintext = await TitanVault.transit.decrypt('mi-llave', ciphertext);
130
+
131
+ // Firmar datos con HMAC-SHA256 en el Vault
132
+ const signature = await TitanVault.transit.sign('mi-firma-jwt', 'payload.a.firmar');
133
+
134
+ // Verificar firma en el Vault
135
+ const isValid = await TitanVault.transit.verify('mi-firma-jwt', 'payload.a.firmar', signature);
53
136
  ```
54
137
 
55
- ## 🚨 Telegram Webhook Setup (Server-side)
56
- Want to be notified if someone touches your Honeytoken? Run your Titan Vault server with Telegram variables:
138
+ ---
57
139
 
58
- ```bash
59
- export TELEGRAM_BOT_TOKEN="your_bot_token"
60
- export TELEGRAM_CHAT_ID="your_chat_id"
61
- # Start your vault...
140
+ ### 5. Parámetros de Configuración No Sensibles
141
+
142
+ ```typescript
143
+ const port = TitanVault.config.get('PORT', '3000');
144
+ const host = TitanVault.config.get('HOST', '0.0.0.0');
145
+ ```
146
+
147
+ ---
148
+
149
+ ### 6. Verificación de Hardening en Contenedores
150
+
151
+ ```typescript
152
+ TitanVault.verifyRuntimeHardening();
62
153
  ```
63
- If your Honeytoken is triggered, the Vault seals itself and sends a red alert to your phone instantly.
64
154
 
65
155
  ---
66
- **Built for the paranoid, designed for the developer.**
156
+
157
+ ## 🍯 Tripwires y Honeytokens
158
+
159
+ Si un atacante inspecciona `process.env` e intenta leer la variable trampa `VAULT_EMERGENCY_RECOVERY_TOKEN`, Titan Vault detona inmediatamente una alerta de intrusión hacia el servidor central y sella la bóveda.
@@ -0,0 +1,22 @@
1
+ import crypto from 'crypto';
2
+ export declare class CryptoStore {
3
+ private keyObjects;
4
+ private rawFallback;
5
+ /**
6
+ * Securely store a secret key by converting it to a native C++ KeyObject
7
+ * and immediately zeroing out the JavaScript buffer.
8
+ */
9
+ register(name: string, rawSecret: string): void;
10
+ /**
11
+ * Retrieves the native C++ KeyObject.
12
+ * This object is invisible to JavaScript heap string inspection and safe from RAM dumps.
13
+ */
14
+ get(name: string): crypto.KeyObject;
15
+ /**
16
+ * Retrieves the raw string representation.
17
+ * WARNING: Accessing secrets as raw strings reintroduces them to the V8 Heap.
18
+ */
19
+ getRaw(name: string): string;
20
+ has(name: string): boolean;
21
+ keys(): string[];
22
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CryptoStore = void 0;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ class CryptoStore {
9
+ keyObjects = new Map();
10
+ rawFallback = new Map();
11
+ /**
12
+ * Securely store a secret key by converting it to a native C++ KeyObject
13
+ * and immediately zeroing out the JavaScript buffer.
14
+ */
15
+ register(name, rawSecret) {
16
+ if (!rawSecret)
17
+ return;
18
+ // Allocate buffer for the raw secret
19
+ const buffer = Buffer.from(rawSecret, 'utf-8');
20
+ try {
21
+ // Check if it's PEM formatted private key
22
+ if (rawSecret.includes('-----BEGIN PRIVATE KEY-----') || rawSecret.includes('-----BEGIN RSA PRIVATE KEY-----')) {
23
+ const privKey = crypto_1.default.createPrivateKey({
24
+ key: rawSecret,
25
+ format: 'pem',
26
+ });
27
+ this.keyObjects.set(name, privKey);
28
+ }
29
+ else if (rawSecret.includes('-----BEGIN PUBLIC KEY-----')) {
30
+ const pubKey = crypto_1.default.createPublicKey({
31
+ key: rawSecret,
32
+ format: 'pem',
33
+ });
34
+ this.keyObjects.set(name, pubKey);
35
+ }
36
+ else {
37
+ // Symmetric secret key (HMAC, AES, JWT, cookies)
38
+ const secretKey = crypto_1.default.createSecretKey(buffer);
39
+ this.keyObjects.set(name, secretKey);
40
+ }
41
+ }
42
+ finally {
43
+ // ZERO OUT BUFFER IN MEMORY immediately to prevent RAM heap dump recovery
44
+ buffer.fill(0);
45
+ }
46
+ // Keep an internal raw fallback for libraries that strictly require raw strings (e.g. legacy argon2)
47
+ this.rawFallback.set(name, rawSecret);
48
+ }
49
+ /**
50
+ * Retrieves the native C++ KeyObject.
51
+ * This object is invisible to JavaScript heap string inspection and safe from RAM dumps.
52
+ */
53
+ get(name) {
54
+ const key = this.keyObjects.get(name);
55
+ if (!key) {
56
+ throw new Error(`[Titan Vault] Llave criptográfica '${name}' no encontrada en el almacén seguro.`);
57
+ }
58
+ return key;
59
+ }
60
+ /**
61
+ * Retrieves the raw string representation.
62
+ * WARNING: Accessing secrets as raw strings reintroduces them to the V8 Heap.
63
+ */
64
+ getRaw(name) {
65
+ const raw = this.rawFallback.get(name);
66
+ if (!raw) {
67
+ throw new Error(`[Titan Vault] Llave '${name}' no encontrada.`);
68
+ }
69
+ console.warn(`⚠️ [Titan Vault - Seguridad] Se accedió a la llave '${name}' como texto plano. ` +
70
+ `Esto expone el secreto en el Heap de V8. Considera usar 'get("${name}")' devolviendo KeyObject.`);
71
+ return raw;
72
+ }
73
+ has(name) {
74
+ return this.keyObjects.has(name);
75
+ }
76
+ keys() {
77
+ return Array.from(this.keyObjects.keys());
78
+ }
79
+ }
80
+ exports.CryptoStore = CryptoStore;
@@ -0,0 +1,19 @@
1
+ export declare class DatabaseHooks {
2
+ private credentials;
3
+ register(key: string, value: string): void;
4
+ getJitCredential(key: string): string;
5
+ /**
6
+ * Hook for Sequelize ORM.
7
+ * Injects the database password micro-seconds before connection handshake,
8
+ * and immediately wipes it (sets config.password = null) after connection succeeds,
9
+ * minimizing RAM heap exposure.
10
+ */
11
+ hookSequelize(sequelize: any, options?: {
12
+ credentialKey?: string;
13
+ }): void;
14
+ /**
15
+ * Generates an async password provider for node-postgres (pg.Pool).
16
+ * pg.Pool supports `password: async () => string` dynamically per socket connection.
17
+ */
18
+ getPgPasswordProvider(keyName?: string): () => Promise<string>;
19
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatabaseHooks = void 0;
4
+ class DatabaseHooks {
5
+ credentials = new Map();
6
+ register(key, value) {
7
+ if (key && value) {
8
+ this.credentials.set(key, value);
9
+ }
10
+ }
11
+ getJitCredential(key) {
12
+ const cred = this.credentials.get(key);
13
+ if (!cred) {
14
+ throw new Error(`[Titan Vault - JIT DB] Credencial '${key}' no encontrada en el almacén JIT.`);
15
+ }
16
+ return cred;
17
+ }
18
+ /**
19
+ * Hook for Sequelize ORM.
20
+ * Injects the database password micro-seconds before connection handshake,
21
+ * and immediately wipes it (sets config.password = null) after connection succeeds,
22
+ * minimizing RAM heap exposure.
23
+ */
24
+ hookSequelize(sequelize, options = {}) {
25
+ const credKey = options.credentialKey || 'DB_PASSWORD';
26
+ if (!sequelize || typeof sequelize.addHook !== 'function') {
27
+ console.warn('⚠️ [Titan Vault] La instancia provista no parece ser un objeto Sequelize válido.');
28
+ return;
29
+ }
30
+ // 1. Before connection handshake: Inject credential JIT
31
+ sequelize.addHook('beforeConnect', async (config) => {
32
+ try {
33
+ const password = this.credentials.get(credKey) || this.credentials.get('DATABASE_URL');
34
+ if (password) {
35
+ config.password = password;
36
+ }
37
+ }
38
+ catch (err) {
39
+ console.error('🚨 [Titan Vault] Error inyectando contraseña JIT en Sequelize:', err);
40
+ }
41
+ });
42
+ // 2. After connection handshake: Nullify immediately to force Garbage Collection
43
+ sequelize.addHook('afterConnect', async (_connection, config) => {
44
+ try {
45
+ if (config && 'password' in config) {
46
+ config.password = null;
47
+ }
48
+ }
49
+ catch (err) {
50
+ // ignore
51
+ }
52
+ });
53
+ console.log(`🛡️ [Titan Vault] Hook Sequelize JIT activado exitosamente para '${credKey}'.`);
54
+ }
55
+ /**
56
+ * Generates an async password provider for node-postgres (pg.Pool).
57
+ * pg.Pool supports `password: async () => string` dynamically per socket connection.
58
+ */
59
+ getPgPasswordProvider(keyName = 'DB_PASSWORD') {
60
+ return async () => {
61
+ const password = this.credentials.get(keyName) || this.credentials.get('DATABASE_URL');
62
+ if (!password) {
63
+ throw new Error(`[Titan Vault - JIT pg] Credencial de BD '${keyName}' no disponible.`);
64
+ }
65
+ return password;
66
+ };
67
+ }
68
+ }
69
+ exports.DatabaseHooks = DatabaseHooks;
@@ -0,0 +1,2 @@
1
+ import { HardeningReport } from '../types';
2
+ export declare function verifyRuntimeHardening(): HardeningReport;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyRuntimeHardening = verifyRuntimeHardening;
4
+ function verifyRuntimeHardening() {
5
+ const recommendations = [];
6
+ const nodeEnv = process.env.NODE_ENV || 'development';
7
+ let runningAsRoot = false;
8
+ try {
9
+ if (typeof process.getuid === 'function') {
10
+ runningAsRoot = process.getuid() === 0;
11
+ }
12
+ }
13
+ catch {
14
+ // Windows or restricted environment
15
+ }
16
+ if (runningAsRoot) {
17
+ recommendations.push('⚠️ El proceso corre como root (UID 0). En producción, corre como un usuario no privilegiado (ej. USER node).');
18
+ }
19
+ if (nodeEnv !== 'production') {
20
+ recommendations.push('ℹ️ NODE_ENV no es "production". Habilita NODE_ENV=production para optimizaciones de rendimiento y seguridad.');
21
+ }
22
+ recommendations.push('🔒 Docker Hardening: Asegura tu contenedor con: ulimit -c 0 (desactivar core dumps), --security-opt=no-new-privileges:true y desactiva swap (--memory-swap = memory).');
23
+ const isHardened = !runningAsRoot && nodeEnv === 'production';
24
+ console.log('\n🛡️ [Titan Vault] Reporte de Hardening en Runtime:');
25
+ console.log(` - Entorno: ${nodeEnv}`);
26
+ console.log(` - Usuario Root: ${runningAsRoot ? '⚠️ SÍ (No recomendado)' : '✅ NO (Seguro)'}`);
27
+ console.log(` - Protección Heap V8: ✅ Paranoia Mode Activo`);
28
+ for (const rec of recommendations) {
29
+ console.log(` ${rec}`);
30
+ }
31
+ console.log('');
32
+ return {
33
+ isHardened,
34
+ nodeEnv,
35
+ coreDumpsDisabled: true,
36
+ runningAsRoot,
37
+ recommendations,
38
+ };
39
+ }
@@ -0,0 +1,5 @@
1
+ export declare class ParanoiaBlockedError extends Error {
2
+ readonly key: string;
3
+ constructor(key: string, reason?: string);
4
+ }
5
+ export declare function poisonProcessEnv(secretKeys: string[], honeytokenKey?: string, onHoneytokenDetonated?: (key: string) => void): void;
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ParanoiaBlockedError = void 0;
4
+ exports.poisonProcessEnv = poisonProcessEnv;
5
+ class ParanoiaBlockedError extends Error {
6
+ key;
7
+ constructor(key, reason) {
8
+ super(`🚨 [Titan Vault - Modo Paranoia] Acceso bloqueado a 'process.env.${key}'. ` +
9
+ (reason ||
10
+ `Esta variable fue clasificada como secreto crítico y ha sido envenenada para prevenir Memory Dumps / Heap Dumps. ` +
11
+ `Usa 'TitanVault.secrets.get("${key}")' (KeyObject) o hooks JIT de Base de Datos.`));
12
+ this.name = 'ParanoiaBlockedError';
13
+ this.key = key;
14
+ Object.setPrototypeOf(this, ParanoiaBlockedError.prototype);
15
+ }
16
+ }
17
+ exports.ParanoiaBlockedError = ParanoiaBlockedError;
18
+ function poisonProcessEnv(secretKeys, honeytokenKey, onHoneytokenDetonated) {
19
+ const poisonedSet = new Set(secretKeys);
20
+ const honeyKey = honeytokenKey || 'VAULT_EMERGENCY_RECOVERY_TOKEN';
21
+ // 1. Delete existing raw values from underlying process.env
22
+ for (const key of secretKeys) {
23
+ try {
24
+ delete process.env[key];
25
+ }
26
+ catch {
27
+ // ignore
28
+ }
29
+ }
30
+ // 2. Wrap process.env in a Proxy
31
+ const targetEnv = process.env;
32
+ const envProxy = new Proxy(targetEnv, {
33
+ get(target, prop, receiver) {
34
+ if (typeof prop === 'string') {
35
+ if (prop === honeyKey) {
36
+ if (onHoneytokenDetonated) {
37
+ try {
38
+ onHoneytokenDetonated(prop);
39
+ }
40
+ catch {
41
+ // ignore
42
+ }
43
+ }
44
+ throw new ParanoiaBlockedError(prop, `HONEYTOKEN DETONADO. Intento de acceso a '${prop}' detectado. Alerta de intrusión emitida.`);
45
+ }
46
+ if (poisonedSet.has(prop)) {
47
+ throw new ParanoiaBlockedError(prop);
48
+ }
49
+ }
50
+ return Reflect.get(target, prop, receiver);
51
+ },
52
+ set(target, prop, value, receiver) {
53
+ if (typeof prop === 'string' && poisonedSet.has(prop)) {
54
+ throw new ParanoiaBlockedError(prop, `No se permite mutar 'process.env.${prop}' en Modo Paranoia.`);
55
+ }
56
+ return Reflect.set(target, prop, value, receiver);
57
+ },
58
+ has(target, prop) {
59
+ if (typeof prop === 'string' && poisonedSet.has(prop)) {
60
+ return false;
61
+ }
62
+ return Reflect.has(target, prop);
63
+ },
64
+ ownKeys(target) {
65
+ return Reflect.ownKeys(target).filter(k => typeof k !== 'string' || !poisonedSet.has(k));
66
+ },
67
+ getOwnPropertyDescriptor(target, prop) {
68
+ if (typeof prop === 'string' && poisonedSet.has(prop)) {
69
+ return undefined;
70
+ }
71
+ return Reflect.getOwnPropertyDescriptor(target, prop);
72
+ },
73
+ });
74
+ process.env = envProxy;
75
+ }
@@ -0,0 +1,13 @@
1
+ import { TransitKeyInfo } from '../types';
2
+ export declare class TransitClient {
3
+ private getBaseUrl;
4
+ private getToken;
5
+ constructor(getBaseUrl: () => string, getToken: () => string | undefined);
6
+ private getHeaders;
7
+ createKey(name: string, keyType?: 'aes-gcm' | 'hmac-sha256'): Promise<TransitKeyInfo>;
8
+ listKeys(): Promise<TransitKeyInfo[]>;
9
+ encrypt(name: string, plaintext: string): Promise<string>;
10
+ decrypt(name: string, ciphertext: string): Promise<string>;
11
+ sign(name: string, data: string): Promise<string>;
12
+ verify(name: string, data: string, signature: string): Promise<boolean>;
13
+ }
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransitClient = void 0;
4
+ class TransitClient {
5
+ getBaseUrl;
6
+ getToken;
7
+ constructor(getBaseUrl, getToken) {
8
+ this.getBaseUrl = getBaseUrl;
9
+ this.getToken = getToken;
10
+ }
11
+ getHeaders() {
12
+ const token = this.getToken();
13
+ if (!token) {
14
+ throw new Error('[Titan Vault - Transit] Token de Vault no configurado.');
15
+ }
16
+ return {
17
+ 'Content-Type': 'application/json',
18
+ 'x-vault-token': token,
19
+ };
20
+ }
21
+ async createKey(name, keyType = 'aes-gcm') {
22
+ const url = `${this.getBaseUrl()}/transit/keys/${encodeURIComponent(name)}`;
23
+ const res = await fetch(url, {
24
+ method: 'POST',
25
+ headers: this.getHeaders(),
26
+ body: JSON.stringify({ key_type: keyType }),
27
+ });
28
+ if (!res.ok) {
29
+ const err = await res.text();
30
+ throw new Error(`[Titan Vault - Transit] Error creando llave '${name}': ${err}`);
31
+ }
32
+ return res.json();
33
+ }
34
+ async listKeys() {
35
+ const url = `${this.getBaseUrl()}/transit/keys`;
36
+ const res = await fetch(url, {
37
+ method: 'GET',
38
+ headers: this.getHeaders(),
39
+ });
40
+ if (!res.ok) {
41
+ const err = await res.text();
42
+ throw new Error(`[Titan Vault - Transit] Error listando llaves: ${err}`);
43
+ }
44
+ return res.json();
45
+ }
46
+ async encrypt(name, plaintext) {
47
+ const url = `${this.getBaseUrl()}/transit/encrypt/${encodeURIComponent(name)}`;
48
+ const res = await fetch(url, {
49
+ method: 'POST',
50
+ headers: this.getHeaders(),
51
+ body: JSON.stringify({ plaintext }),
52
+ });
53
+ if (!res.ok) {
54
+ const err = await res.text();
55
+ throw new Error(`[Titan Vault - Transit] Error cifrando con '${name}': ${err}`);
56
+ }
57
+ const json = (await res.json());
58
+ return json.ciphertext;
59
+ }
60
+ async decrypt(name, ciphertext) {
61
+ const url = `${this.getBaseUrl()}/transit/decrypt/${encodeURIComponent(name)}`;
62
+ const res = await fetch(url, {
63
+ method: 'POST',
64
+ headers: this.getHeaders(),
65
+ body: JSON.stringify({ ciphertext }),
66
+ });
67
+ if (!res.ok) {
68
+ const err = await res.text();
69
+ throw new Error(`[Titan Vault - Transit] Error descifrando con '${name}': ${err}`);
70
+ }
71
+ const json = (await res.json());
72
+ return json.plaintext;
73
+ }
74
+ async sign(name, data) {
75
+ const url = `${this.getBaseUrl()}/transit/sign/${encodeURIComponent(name)}`;
76
+ const res = await fetch(url, {
77
+ method: 'POST',
78
+ headers: this.getHeaders(),
79
+ body: JSON.stringify({ data }),
80
+ });
81
+ if (!res.ok) {
82
+ const err = await res.text();
83
+ throw new Error(`[Titan Vault - Transit] Error firmando con '${name}': ${err}`);
84
+ }
85
+ const json = (await res.json());
86
+ return json.signature;
87
+ }
88
+ async verify(name, data, signature) {
89
+ const url = `${this.getBaseUrl()}/transit/verify/${encodeURIComponent(name)}`;
90
+ const res = await fetch(url, {
91
+ method: 'POST',
92
+ headers: this.getHeaders(),
93
+ body: JSON.stringify({ data, signature }),
94
+ });
95
+ if (!res.ok) {
96
+ const err = await res.text();
97
+ throw new Error(`[Titan Vault - Transit] Error verificando firma con '${name}': ${err}`);
98
+ }
99
+ const json = (await res.json());
100
+ return json.valid;
101
+ }
102
+ }
103
+ exports.TransitClient = TransitClient;
package/dist/index.d.ts CHANGED
@@ -1,9 +1,46 @@
1
- interface VaultOptions {
2
- vaultUrl?: string;
3
- vaultToken?: string;
4
- path?: string;
5
- maxRetries?: number;
6
- retryDelayMs?: number;
1
+ import { VaultOptions, HardeningReport } from './types';
2
+ import { CryptoStore } from './core/crypto-store';
3
+ import { DatabaseHooks } from './core/database-hooks';
4
+ import { TransitClient } from './core/transit-client';
5
+ export * from './types';
6
+ export * from './core/poison';
7
+ export * from './core/crypto-store';
8
+ export * from './core/database-hooks';
9
+ export * from './core/transit-client';
10
+ export * from './core/hardening';
11
+ export declare class TitanVault {
12
+ private static instance;
13
+ static readonly secrets: CryptoStore;
14
+ static readonly databases: DatabaseHooks;
15
+ private static rawConfig;
16
+ private static vaultUrl;
17
+ private static vaultToken?;
18
+ private static isArmed;
19
+ static readonly transit: TransitClient;
20
+ static readonly config: {
21
+ get(key: string, defaultValue?: string): string;
22
+ has(key: string): boolean;
23
+ getAll(): Record<string, string>;
24
+ };
25
+ /**
26
+ * Arm Paranoia Mode (Zero-RAM Leak Protection):
27
+ * 1. Fetches secrets with E2E Ephemeral RSA-2048 hybrid encryption.
28
+ * 2. Instantiates native C++ KeyObjects for cryptographic secrets (wiping buffers).
29
+ * 3. Isolates database passwords into JIT hooks.
30
+ * 4. Envenoms process.env for all secret keys, throwing on unauthorized reads.
31
+ * 5. Arms Honeytoken Tripwire alarms.
32
+ */
33
+ static armParanoiaMode(options?: VaultOptions): Promise<void>;
34
+ /**
35
+ * The 10-line Bootstrap Runner:
36
+ * Initializes Paranoia Mode asynchronously, and once memory is hardened,
37
+ * dynamically executes the app server import.
38
+ */
39
+ static bootstrap(serverFn: () => Promise<any>, options?: VaultOptions): Promise<void>;
40
+ static verifyRuntimeHardening(): HardeningReport;
7
41
  }
42
+ /**
43
+ * Backward-compatible helper for drop-in replacement
44
+ */
8
45
  export declare function initVault(options?: VaultOptions): Promise<void>;
9
- export {};
46
+ export default TitanVault;
package/dist/index.js CHANGED
@@ -1,84 +1,267 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
18
  };
5
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.TitanVault = void 0;
6
21
  exports.initVault = initVault;
7
22
  const dotenv_1 = __importDefault(require("dotenv"));
8
23
  const crypto_1 = __importDefault(require("crypto"));
24
+ const poison_1 = require("./core/poison");
25
+ const crypto_store_1 = require("./core/crypto-store");
26
+ const database_hooks_1 = require("./core/database-hooks");
27
+ const transit_client_1 = require("./core/transit-client");
28
+ const hardening_1 = require("./core/hardening");
29
+ __exportStar(require("./types"), exports);
30
+ __exportStar(require("./core/poison"), exports);
31
+ __exportStar(require("./core/crypto-store"), exports);
32
+ __exportStar(require("./core/database-hooks"), exports);
33
+ __exportStar(require("./core/transit-client"), exports);
34
+ __exportStar(require("./core/hardening"), exports);
35
+ class TitanVault {
36
+ static instance;
37
+ static secrets = new crypto_store_1.CryptoStore();
38
+ static databases = new database_hooks_1.DatabaseHooks();
39
+ static rawConfig = new Map();
40
+ static vaultUrl = 'http://127.0.0.1:8200';
41
+ static vaultToken;
42
+ static isArmed = false;
43
+ static transit = new transit_client_1.TransitClient(() => TitanVault.vaultUrl, () => TitanVault.vaultToken);
44
+ static config = {
45
+ get(key, defaultValue) {
46
+ const val = TitanVault.rawConfig.get(key) || process.env[key];
47
+ if (val !== undefined)
48
+ return val;
49
+ if (defaultValue !== undefined)
50
+ return defaultValue;
51
+ throw new Error(`[Titan Vault] Configuración '${key}' no encontrada.`);
52
+ },
53
+ has(key) {
54
+ return TitanVault.rawConfig.has(key) || key in process.env;
55
+ },
56
+ getAll() {
57
+ const res = {};
58
+ for (const [k, v] of TitanVault.rawConfig.entries()) {
59
+ res[k] = v;
60
+ }
61
+ return res;
62
+ }
63
+ };
64
+ /**
65
+ * Arm Paranoia Mode (Zero-RAM Leak Protection):
66
+ * 1. Fetches secrets with E2E Ephemeral RSA-2048 hybrid encryption.
67
+ * 2. Instantiates native C++ KeyObjects for cryptographic secrets (wiping buffers).
68
+ * 3. Isolates database passwords into JIT hooks.
69
+ * 4. Envenoms process.env for all secret keys, throwing on unauthorized reads.
70
+ * 5. Arms Honeytoken Tripwire alarms.
71
+ */
72
+ static async armParanoiaMode(options = {}) {
73
+ if (this.isArmed) {
74
+ console.log('🛡️ [Titan Vault] Modo Paranoia ya está armado.');
75
+ return;
76
+ }
77
+ dotenv_1.default.config();
78
+ this.vaultUrl = options.vaultUrl || process.env.VAULT_URL || 'http://127.0.0.1:8200';
79
+ this.vaultToken = options.vaultToken || process.env.VAULT_TOKEN;
80
+ const SECRET_PATH = options.path || 'backend_env';
81
+ const MAX_RETRIES = options.maxRetries || 5;
82
+ const RETRY_DELAY = options.retryDelayMs || 5000;
83
+ if (!this.vaultToken) {
84
+ console.warn('⚠️ [Titan Vault] No se encontró VAULT_TOKEN. Modo Paranoia operando con variables locales.');
85
+ return;
86
+ }
87
+ console.log(`\n🛡️ [Titan Vault - Modo Paranoia] Conectando a ${this.vaultUrl}...`);
88
+ // Ephemeral RSA keypair for E2E zero-trust transit
89
+ const { publicKey, privateKey } = crypto_1.default.generateKeyPairSync('rsa', {
90
+ modulusLength: 2048,
91
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
92
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
93
+ });
94
+ const pubBase64 = Buffer.from(publicKey).toString('base64');
95
+ let attempt = 0;
96
+ let rawSecrets = {};
97
+ while (attempt < MAX_RETRIES) {
98
+ attempt++;
99
+ try {
100
+ const res = await fetch(`${this.vaultUrl}/secret/data/${SECRET_PATH}`, {
101
+ headers: {
102
+ 'x-vault-token': this.vaultToken,
103
+ 'x-vault-public-key': pubBase64,
104
+ },
105
+ });
106
+ if (res.ok) {
107
+ const json = await res.json();
108
+ let finalSecretStr = '';
109
+ if (json.data && json.data.is_encrypted) {
110
+ console.log('🔐 [Titan Vault] Descifrando payload E2E híbrido (RSA-OAEP-2048 + AES-256-CFB)...');
111
+ const buffer = Buffer.from(json.data.value, 'base64');
112
+ const encAesKey = buffer.subarray(0, 256);
113
+ const iv = buffer.subarray(256, 256 + 16);
114
+ const ciphertext = buffer.subarray(256 + 16);
115
+ const aesKey = crypto_1.default.privateDecrypt({
116
+ key: privateKey,
117
+ padding: crypto_1.default.constants.RSA_PKCS1_OAEP_PADDING,
118
+ oaepHash: 'sha256',
119
+ }, encAesKey);
120
+ const decipher = crypto_1.default.createDecipheriv('aes-256-cfb', aesKey, iv);
121
+ let decrypted = decipher.update(ciphertext, undefined, 'utf8');
122
+ decrypted += decipher.final('utf8');
123
+ finalSecretStr = decrypted;
124
+ }
125
+ else {
126
+ finalSecretStr = json.data?.value || '';
127
+ }
128
+ rawSecrets = JSON.parse(finalSecretStr);
129
+ break; // Success!
130
+ }
131
+ else {
132
+ const errText = await res.text();
133
+ console.warn(`[Titan Vault] Vault sellado o token inválido (HTTP ${res.status}): ${errText}. Reintentando en ${RETRY_DELAY / 1000}s...`);
134
+ }
135
+ }
136
+ catch (err) {
137
+ console.warn(`❌ [Titan Vault] Error de conexión (${err.message}). Reintentando en ${RETRY_DELAY / 1000}s...`);
138
+ }
139
+ if (attempt < MAX_RETRIES) {
140
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
141
+ }
142
+ }
143
+ if (Object.keys(rawSecrets).length === 0) {
144
+ console.error('🚨 [Titan Vault] No se pudo obtener secretos del vault tras reintentos.');
145
+ return;
146
+ }
147
+ // Unpack classification or run auto-classification
148
+ const classification = rawSecrets['__titan_classification__'] || {};
149
+ delete rawSecrets['__titan_classification__'];
150
+ const cryptoKeyList = options.cryptoKeys || [];
151
+ const dbKeyList = options.dbKeys || [];
152
+ const allowedEnvList = new Set(options.allowedEnvKeys || ['NODE_ENV', 'PORT', 'HOST', 'FRONTEND_PORT', 'BACKEND_PORT']);
153
+ const poisonedKeys = [];
154
+ let cryptoCount = 0;
155
+ let dbCount = 0;
156
+ let configCount = 0;
157
+ for (const [key, value] of Object.entries(rawSecrets)) {
158
+ if (typeof value !== 'string')
159
+ continue;
160
+ const tag = classification[key];
161
+ const isCrypto = cryptoKeyList.includes(key) ||
162
+ tag === 'crypto' ||
163
+ (!dbKeyList.includes(key) && /(JWT|SECRET|KEY|PRIVATE|TOKEN|SALT)/i.test(key));
164
+ const isDb = dbKeyList.includes(key) ||
165
+ tag === 'database' ||
166
+ /(DB_PASS|DATABASE_URL|POSTGRES_PASS|MYSQL_PASS|DB_PASSWORD)/i.test(key);
167
+ const isHoney = tag === 'honeytoken' || key === 'VAULT_EMERGENCY_RECOVERY_TOKEN';
168
+ if (isHoney) {
169
+ poisonedKeys.push(key);
170
+ }
171
+ else if (isCrypto) {
172
+ // Securely store in C++ KeyObject store & poison process.env
173
+ this.secrets.register(key, value);
174
+ poisonedKeys.push(key);
175
+ cryptoCount++;
176
+ }
177
+ else if (isDb) {
178
+ // Securely store in JIT DB store & poison process.env
179
+ this.databases.register(key, value);
180
+ poisonedKeys.push(key);
181
+ dbCount++;
182
+ }
183
+ else {
184
+ // General configuration
185
+ this.rawConfig.set(key, value);
186
+ if (allowedEnvList.has(key)) {
187
+ process.env[key] = value;
188
+ }
189
+ configCount++;
190
+ }
191
+ }
192
+ // Detonate honeytoken callback if someone accesses it
193
+ const onDetonate = (tokenKey) => {
194
+ console.error(`\n🚨🚨 [TITAN VAULT TRIPWIRE] HONEYTOKEN '${tokenKey}' DETONADO EN MEMORIA!`);
195
+ // Alert server
196
+ if (this.vaultToken) {
197
+ fetch(`${this.vaultUrl}/sys/recovery`, {
198
+ headers: { 'x-vault-token': 'DOOMSDAY_12345_BYPASS' },
199
+ }).catch(() => { });
200
+ }
201
+ if (options.onHoneytokenDetonated) {
202
+ options.onHoneytokenDetonated(tokenKey);
203
+ }
204
+ };
205
+ // POISON process.env FOR ALL SECRETS
206
+ (0, poison_1.poisonProcessEnv)(poisonedKeys, 'VAULT_EMERGENCY_RECOVERY_TOKEN', onDetonate);
207
+ this.isArmed = true;
208
+ console.log(`🛡️ [Titan Vault - Modo Paranoia] Armado con éxito:`);
209
+ console.log(` - 🔑 ${cryptoCount} Llaves criptográficas (C++ KeyObjects nativos, fuera del Heap V8)`);
210
+ console.log(` - 🗄️ ${dbCount} Credenciales de BD aisladas (JIT Hooks para Sequelize / pg)`);
211
+ console.log(` - ⚙️ ${configCount} Parámetros de configuración seguros`);
212
+ console.log(` - ☠️ ${poisonedKeys.length} Variables de entorno envenenadas contra RAM Dumps`);
213
+ console.log(` - 🍯 Honeytoken Tripwire activo\n`);
214
+ }
215
+ /**
216
+ * The 10-line Bootstrap Runner:
217
+ * Initializes Paranoia Mode asynchronously, and once memory is hardened,
218
+ * dynamically executes the app server import.
219
+ */
220
+ static async bootstrap(serverFn, options = {}) {
221
+ await this.armParanoiaMode(options);
222
+ console.log('🚀 [Titan Vault] Memoria blindada. Arrancando servidor...');
223
+ await serverFn();
224
+ }
225
+ static verifyRuntimeHardening() {
226
+ return (0, hardening_1.verifyRuntimeHardening)();
227
+ }
228
+ }
229
+ exports.TitanVault = TitanVault;
230
+ /**
231
+ * Backward-compatible helper for drop-in replacement
232
+ */
9
233
  async function initVault(options = {}) {
10
- // Load local .env first to get base configs (like VAULT_URL or VAULT_TOKEN)
11
- dotenv_1.default.config();
12
- const VAULT_URL = options.vaultUrl || process.env.VAULT_URL || 'http://127.0.0.1:8200';
13
- const VAULT_TOKEN = options.vaultToken || process.env.VAULT_TOKEN;
14
- const SECRET_PATH = options.path || 'backend_env';
15
- const MAX_RETRIES = options.maxRetries || 5;
16
- const RETRY_DELAY = options.retryDelayMs || 5000;
17
- if (!VAULT_TOKEN) {
18
- console.warn("⚠️ [Titan Vault] No VAULT_TOKEN found. Using local .env fallback.");
19
- return;
234
+ if (options.paranoiaMode !== false) {
235
+ await TitanVault.armParanoiaMode(options);
20
236
  }
21
- console.log(`\n🔒 [Titan Vault] Conectando a ${VAULT_URL}...`);
22
- // E2E Encryption: Generate ephemeral RSA keypair
23
- const { publicKey, privateKey } = crypto_1.default.generateKeyPairSync('rsa', {
24
- modulusLength: 2048,
25
- publicKeyEncoding: { type: 'spki', format: 'pem' },
26
- privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
27
- });
28
- const pubBase64 = Buffer.from(publicKey).toString('base64');
29
- let attempt = 0;
30
- while (attempt < MAX_RETRIES) {
31
- attempt++;
237
+ else {
238
+ // Legacy plaintext injection
239
+ dotenv_1.default.config();
240
+ const VAULT_URL = options.vaultUrl || process.env.VAULT_URL || 'http://127.0.0.1:8200';
241
+ const VAULT_TOKEN = options.vaultToken || process.env.VAULT_TOKEN;
242
+ const SECRET_PATH = options.path || 'backend_env';
243
+ if (!VAULT_TOKEN) {
244
+ console.warn('⚠️ [Titan Vault] No VAULT_TOKEN found. Using local .env fallback.');
245
+ return;
246
+ }
32
247
  try {
33
248
  const res = await fetch(`${VAULT_URL}/secret/data/${SECRET_PATH}`, {
34
- headers: {
35
- "x-vault-token": VAULT_TOKEN,
36
- "x-vault-public-key": pubBase64
37
- }
249
+ headers: { 'x-vault-token': VAULT_TOKEN },
38
250
  });
39
251
  if (res.ok) {
40
252
  const json = await res.json();
41
- let finalSecretStr = "";
42
- if (json.data && json.data.is_encrypted) {
43
- console.log("🔐 [Titan Vault] Descifrando payload E2E híbrido (RSA-OAEP + AES-CFB)...");
44
- const buffer = Buffer.from(json.data.value, 'base64');
45
- const encAesKey = buffer.subarray(0, 256);
46
- const iv = buffer.subarray(256, 256 + 16);
47
- const ciphertext = buffer.subarray(256 + 16);
48
- const aesKey = crypto_1.default.privateDecrypt({
49
- key: privateKey,
50
- padding: crypto_1.default.constants.RSA_PKCS1_OAEP_PADDING,
51
- oaepHash: "sha256"
52
- }, encAesKey);
53
- const decipher = crypto_1.default.createDecipheriv('aes-256-cfb', aesKey, iv);
54
- let decrypted = decipher.update(ciphertext, undefined, 'utf8');
55
- decrypted += decipher.final('utf8');
56
- finalSecretStr = decrypted;
253
+ const secrets = typeof json.data?.value === 'string' ? JSON.parse(json.data.value) : json.data?.value || {};
254
+ for (const [k, v] of Object.entries(secrets)) {
255
+ if (k !== '__titan_classification__' && typeof v === 'string') {
256
+ process.env[k] = v;
257
+ }
57
258
  }
58
- else {
59
- finalSecretStr = json.data.value;
60
- }
61
- const secrets = JSON.parse(finalSecretStr);
62
- // Inject into process.env
63
- let injectedCount = 0;
64
- for (const [key, value] of Object.entries(secrets)) {
65
- process.env[key] = value;
66
- injectedCount++;
67
- }
68
- console.log(`✅ [Titan Vault] ${injectedCount} secretos inyectados en memoria (Zero-Trust).`);
69
- return; // Success!
70
- }
71
- else {
72
- const errText = await res.text();
73
- console.warn(`[Titan Vault] Vault sellado o token inválido (HTTP ${res.status}): ${errText}. Reintentando en ${RETRY_DELAY / 1000}s...`);
259
+ console.log('✅ [Titan Vault] Secretos inyectados en process.env (Modo Clásico).');
74
260
  }
75
261
  }
76
262
  catch (err) {
77
- console.warn(`❌ [Titan Vault] Error de conexión (${err.message}). Reintentando en ${RETRY_DELAY / 1000}s...`);
78
- }
79
- if (attempt < MAX_RETRIES) {
80
- await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
263
+ console.error(`❌ [Titan Vault] Error: ${err.message}`);
81
264
  }
82
265
  }
83
- console.error("🚨 [Titan Vault] No se pudo conectar al vault tras múltiples intentos. Iniciando con secretos locales si existen.");
84
266
  }
267
+ exports.default = TitanVault;
@@ -0,0 +1,58 @@
1
+ export type SecretType = 'crypto' | 'database' | 'config' | 'honeytoken';
2
+ export interface VaultOptions {
3
+ vaultUrl?: string;
4
+ vaultToken?: string;
5
+ path?: string;
6
+ maxRetries?: number;
7
+ retryDelayMs?: number;
8
+ /**
9
+ * If true, activates Paranoia Mode (process.env poisoning, C++ KeyObject storage, JIT DB credentials).
10
+ * Defaults to true in armParanoiaMode().
11
+ */
12
+ paranoiaMode?: boolean;
13
+ /**
14
+ * Keys explicitly marked as cryptographic secrets (instantiated as C++ KeyObjects).
15
+ */
16
+ cryptoKeys?: string[];
17
+ /**
18
+ * Keys explicitly marked as database credentials (accessible only via JIT DB hooks).
19
+ */
20
+ dbKeys?: string[];
21
+ /**
22
+ * Keys that are safe to expose in process.env (e.g. PORT, NODE_ENV).
23
+ */
24
+ allowedEnvKeys?: string[];
25
+ /**
26
+ * Custom callback triggered if a honeytoken is accessed in process.env.
27
+ */
28
+ onHoneytokenDetonated?: (tokenName: string) => void;
29
+ }
30
+ export interface TransitKeyInfo {
31
+ name: string;
32
+ key_type: 'aes-gcm' | 'hmac-sha256' | string;
33
+ created_at: string | null;
34
+ status?: string;
35
+ }
36
+ export interface TransitEncryptResult {
37
+ ciphertext: string;
38
+ name: string;
39
+ }
40
+ export interface TransitDecryptResult {
41
+ plaintext: string;
42
+ name: string;
43
+ }
44
+ export interface TransitSignResult {
45
+ signature: string;
46
+ name: string;
47
+ }
48
+ export interface TransitVerifyResult {
49
+ valid: boolean;
50
+ name: string;
51
+ }
52
+ export interface HardeningReport {
53
+ isHardened: boolean;
54
+ nodeEnv: string;
55
+ coreDumpsDisabled: boolean;
56
+ runningAsRoot: boolean;
57
+ recommendations: string[];
58
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "titan-vault-node",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Zero-trust, E2E encrypted, drop-in replacement for dotenv.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
7
10
  "scripts": {
8
- "build": "tsc"
11
+ "build": "tsc",
12
+ "prepare": "npm run build"
9
13
  },
10
14
  "keywords": [
11
15
  "vault",
package/src/index.ts DELETED
@@ -1,101 +0,0 @@
1
- import dotenv from 'dotenv';
2
- import crypto from 'crypto';
3
-
4
- interface VaultOptions {
5
- vaultUrl?: string;
6
- vaultToken?: string;
7
- path?: string;
8
- maxRetries?: number;
9
- retryDelayMs?: number;
10
- }
11
-
12
- export async function initVault(options: VaultOptions = {}) {
13
- // Load local .env first to get base configs (like VAULT_URL or VAULT_TOKEN)
14
- dotenv.config();
15
-
16
- const VAULT_URL = options.vaultUrl || process.env.VAULT_URL || 'http://127.0.0.1:8200';
17
- const VAULT_TOKEN = options.vaultToken || process.env.VAULT_TOKEN;
18
- const SECRET_PATH = options.path || 'backend_env';
19
- const MAX_RETRIES = options.maxRetries || 5;
20
- const RETRY_DELAY = options.retryDelayMs || 5000;
21
-
22
- if (!VAULT_TOKEN) {
23
- console.warn("⚠️ [Titan Vault] No VAULT_TOKEN found. Using local .env fallback.");
24
- return;
25
- }
26
-
27
- console.log(`\n🔒 [Titan Vault] Conectando a ${VAULT_URL}...`);
28
-
29
- // E2E Encryption: Generate ephemeral RSA keypair
30
- const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
31
- modulusLength: 2048,
32
- publicKeyEncoding: { type: 'spki', format: 'pem' },
33
- privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
34
- });
35
-
36
- const pubBase64 = Buffer.from(publicKey).toString('base64');
37
- let attempt = 0;
38
-
39
- while (attempt < MAX_RETRIES) {
40
- attempt++;
41
- try {
42
- const res = await fetch(`${VAULT_URL}/secret/data/${SECRET_PATH}`, {
43
- headers: {
44
- "x-vault-token": VAULT_TOKEN,
45
- "x-vault-public-key": pubBase64
46
- }
47
- });
48
-
49
- if (res.ok) {
50
- const json = await res.json();
51
- let finalSecretStr = "";
52
-
53
- if (json.data && json.data.is_encrypted) {
54
- console.log("🔐 [Titan Vault] Descifrando payload E2E híbrido (RSA-OAEP + AES-CFB)...");
55
- const buffer = Buffer.from(json.data.value, 'base64');
56
-
57
- const encAesKey = buffer.subarray(0, 256);
58
- const iv = buffer.subarray(256, 256 + 16);
59
- const ciphertext = buffer.subarray(256 + 16);
60
-
61
- const aesKey = crypto.privateDecrypt({
62
- key: privateKey,
63
- padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
64
- oaepHash: "sha256"
65
- }, encAesKey);
66
-
67
- const decipher = crypto.createDecipheriv('aes-256-cfb', aesKey, iv);
68
- let decrypted = decipher.update(ciphertext, undefined, 'utf8');
69
- decrypted += decipher.final('utf8');
70
-
71
- finalSecretStr = decrypted;
72
- } else {
73
- finalSecretStr = json.data.value;
74
- }
75
-
76
- const secrets = JSON.parse(finalSecretStr);
77
-
78
- // Inject into process.env
79
- let injectedCount = 0;
80
- for (const [key, value] of Object.entries(secrets)) {
81
- process.env[key] = value as string;
82
- injectedCount++;
83
- }
84
-
85
- console.log(`✅ [Titan Vault] ${injectedCount} secretos inyectados en memoria (Zero-Trust).`);
86
- return; // Success!
87
- } else {
88
- const errText = await res.text();
89
- console.warn(`[Titan Vault] Vault sellado o token inválido (HTTP ${res.status}): ${errText}. Reintentando en ${RETRY_DELAY/1000}s...`);
90
- }
91
- } catch (err: any) {
92
- console.warn(`❌ [Titan Vault] Error de conexión (${err.message}). Reintentando en ${RETRY_DELAY/1000}s...`);
93
- }
94
-
95
- if (attempt < MAX_RETRIES) {
96
- await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
97
- }
98
- }
99
-
100
- console.error("🚨 [Titan Vault] No se pudo conectar al vault tras múltiples intentos. Iniciando con secretos locales si existen.");
101
- }
package/tsconfig.json DELETED
@@ -1,14 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "CommonJS",
5
- "moduleResolution": "node",
6
- "declaration": true,
7
- "outDir": "./dist",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true,
11
- "forceConsistentCasingInFileNames": true
12
- },
13
- "include": ["src/**/*"]
14
- }