titan-vault-node 0.0.0-stage → 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,3 +1,159 @@
1
- # Temporary Holding Version
1
+ # 🛡️ Titan Vault (Node.js SDK) — Modo Paranoia & Zero-Trust
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **SDK de seguridad Zero-Trust, Criptografía E2E, Envenenamiento de `process.env` y Protección de Memoria RAM.**
4
+
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
+
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
32
+
33
+ ### 1. El Patrón Bootstrap (`bootstrap.js` o `src/index.ts`)
34
+
35
+ Crea un archivo de arranque que cargue el SDK antes de importar el servidor:
36
+
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
+ }
52
+
53
+ main();
54
+ ```
55
+
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:
61
+
62
+ ```typescript
63
+ import jwt from 'jsonwebtoken';
64
+ import { TitanVault } from 'titan-vault-node';
65
+
66
+ // Obtiene el KeyObject de C++ (Invisible en volcados de RAM)
67
+ const jwtKey = TitanVault.secrets.get('JWT_SECRET');
68
+
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);
77
+ ```
78
+
79
+ ---
80
+
81
+ ### 3. Conexión Segura a Base de Datos (JIT)
82
+
83
+ #### Con Sequelize:
84
+ ```typescript
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
+ ```
101
+
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
+ ```
115
+
116
+ ---
117
+
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);
136
+ ```
137
+
138
+ ---
139
+
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();
153
+ ```
154
+
155
+ ---
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;
@@ -0,0 +1,46 @@
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;
41
+ }
42
+ /**
43
+ * Backward-compatible helper for drop-in replacement
44
+ */
45
+ export declare function initVault(options?: VaultOptions): Promise<void>;
46
+ export default TitanVault;
package/dist/index.js ADDED
@@ -0,0 +1,267 @@
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
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.TitanVault = void 0;
21
+ exports.initVault = initVault;
22
+ const dotenv_1 = __importDefault(require("dotenv"));
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
+ */
233
+ async function initVault(options = {}) {
234
+ if (options.paranoiaMode !== false) {
235
+ await TitanVault.armParanoiaMode(options);
236
+ }
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
+ }
247
+ try {
248
+ const res = await fetch(`${VAULT_URL}/secret/data/${SECRET_PATH}`, {
249
+ headers: { 'x-vault-token': VAULT_TOKEN },
250
+ });
251
+ if (res.ok) {
252
+ const json = await res.json();
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
+ }
258
+ }
259
+ console.log('✅ [Titan Vault] Secretos inyectados en process.env (Modo Clásico).');
260
+ }
261
+ }
262
+ catch (err) {
263
+ console.error(`❌ [Titan Vault] Error: ${err.message}`);
264
+ }
265
+ }
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,6 +1,31 @@
1
1
  {
2
2
  "name": "titan-vault-node",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.0.0",
4
+ "description": "Zero-trust, E2E encrypted, drop-in replacement for dotenv.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepare": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "vault",
16
+ "security",
17
+ "dotenv",
18
+ "secrets",
19
+ "zero-trust",
20
+ "e2e"
21
+ ],
22
+ "author": "Titan Vault Community",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "dotenv": "^16.4.5"
26
+ },
27
+ "devDependencies": {
28
+ "typescript": "^5.6.3",
29
+ "@types/node": "^22.0.0"
30
+ }
31
+ }