# @domotronik/secure-vault — 0.1.0 SDK ES Modules y declaraciones TypeScript, sin dependencias runtime. Cifrado y descifrado con Web Crypto en cliente. API prevista: `https://securevault.dmtk-corp.com`. Comprobar el recibo de despliegue para confirmar disponibilidad real. ## Instalación ```sh npm install https://securevault.dmtk-corp.com/sdk/domotronik-secure-vault-0.1.0.tgz ``` Para fijar integridad, descargar el paquete y verificar SHA-256 frente al recibo de release antes de instalar. npm genera lockfile con integridad. No se publica en el registro npm sin credenciales y autorización de ese registro. Alternativa sin bundler: importar `https://securevault.dmtk-corp.com/sdk/secure-vault.js` desde un origen HTTPS permitido por CORS. Recomendado distribuir una copia verificada del SDK con tu aplicación y fijar su versión para controlar la cadena de suministro. ## Responsabilidades de integración La API almacena ciphertext y añade una envoltura exterior KMS. No guarda participaciones, claves de usuario, passwords ni plaintext. Sin ambas participaciones y su asociación exacta no hay recuperación. Recovery y Web3Auth NO están implementados en esta entrega. `API_SUITE` identifica las primitivas A actuales; no una suite de encapsulado Web3Auth aprobada. Cada versión produce U/T nuevas e independientes. La aplicación debe proporcionar dos mecanismos de custodia/autorización: uno del usuario para U y otro empresarial para T, ambos vinculados al contexto completo. No guardar U junto al ciphertext en claro ni almacenar ambos factores en el mismo backend. Este SDK no implementa por ti el encapsulado/custodia persistente de U; elegirlo y revisarlo antes de usar datos reales. El SDK HTTP también sirve para ciphertext producido por estas primitivas, sin depender de Astro o Web3Auth. No poner material secreto en localStorage, sessionStorage, IndexedDB, URL, logs, analytics ni bundle. No enviar el objeto completo que devuelve `prepareRevision` a Vault. Las participaciones solo se entregan por sus canales autenticados independientes; Vault recibe exclusivamente `.envelope`. ## Backend del tenant La credencial `svt.*` solo reside en Secret Manager o configuración protegida de TU backend. El alta/rotación se hace mediante CLI administrativa Cloud autorizada; no existe alta pública. Tu backend autentica al usuario mediante tu sistema existente, determina `subjectId` desde su sesión verificada y emite un grant de alcance mínimo. Nunca confiar en subjectId recibido sin autorización del navegador. ```ts import { TenantVaultClient } from '@domotronik/secure-vault/server'; const tenant = new TenantVaultClient({ getToken: async () => readTenantCredentialFromSecretManager(), }); // authenticatedSession procede de tu middleware real de autenticación. const grant = await tenant.issueGrant({ subjectId: authenticatedSession.vaultSubjectId, scopes: ['vault:create', 'vault:read', 'record:create', 'record:read'], vaultIds: [], // Sin lista: todos los vaults de ESE sujeto; nunca otros sujetos. lifetimeSeconds: 300, }); // Devolver solo el grant al usuario autenticado; response Cache-Control:no-store. ``` Para limitar a vaults concretos, pasar sus IDs. Validar ownership antes de concederlos. Cada grant dura 30–900 segundos y es revocable mediante `revokeGrant(jti)`. Rotar la credencial invalida los grants previos del tenant. La clave server permite acceso al ciphertext del tenant, nunca descifrado interior por sí sola. ## Navegador ```ts import { SecureVaultClient, prepareRevision, openRevision } from '@domotronik/secure-vault'; const client = new SecureVaultClient({ getToken: () => getGrantFromYourAuthenticatedBackend() }); const vault = await client.createVault(subjectId, crypto.randomUUID()); const context = { protocolVersion: 'SVP-1' as const, tenantId, subjectId, vaultId: vault.vaultId, recordId: crypto.randomUUID(), revision: 1, }; const prepared = await prepareRevision(context, new TextEncoder().encode('datos')); try { // Contratos de integración propios, NO funciones incluidas por este SDK: await userFactorStore.protectAndPersist(context, prepared.userParticipation); await tenantFactorService.persistAuthorized(context, prepared.tenantParticipation); const record = await client.createRecord(prepared.envelope, crypto.randomUUID()); const downloaded = await client.getRecord(vault.vaultId, record.recordId); const u = await userFactorStore.recoverAuthorized(downloaded.envelope.context); const t = await tenantFactorService.obtainAuthorized(downloaded.envelope.context); try { const bytes = await openRevision(downloaded.envelope, u, t); // Exportar como bytes; nunca ejecutar HTML/SVG/JS recuperado. } finally { u.fill(0); t.fill(0); } } finally { prepared.userParticipation.fill(0); prepared.tenantParticipation.fill(0); } ``` Las funciones de identidad/custodia del ejemplo las proporciona tu integración. No son mocks incluidos ni un sistema de recuperación ya disponible. Guardar factores antes del envelope evita registros irrecuperables si falla su persistencia. Resolver resultados desconocidos con la misma petición/clave idempotente y conservar los factores originales: no regenerarlos durante un reintento. Una operación fallida puede dejar factores huérfanos en tus servicios; reconciliar por recordId/revisión. ## Actualización, borrado y reintentos - Actualizar: usar contexto con `revision + 1`, nuevo `prepareRevision`, guardar nuevos factores y llamar `updateRecord(envelope, etagAnterior, idempotencyKey)`. Un 412 obliga a releer, generar material nuevo y resolver el conflicto, sin sobrescribir revisiones antiguas. - Leer una revisión anterior: `getRecord(vaultId, recordId, revision)`. La respuesta incluye metadata actual y envelope de la revisión solicitada; los factores se buscan por el contexto del envelope. - Borrar: `deleteRecord(vaultId, recordId, etag)`. Es tombstone; bloquea lecturas, también históricas. No es borrado físico inmediato ni libera cuota retenida. - No hay reintentos automáticos de escrituras. Conservar envelope, If-Match e Idempotency-Key exactos durante 24 horas. Mismo key con otro body produce 409. No guardar factores en almacenamiento inseguro para facilitar reintentos. - No introducir plaintext en IDs, `subjectId` o claves idempotentes. IDs opacos ASCII; cursores opacos autenticados. No enviar email como subjectId. - Límite actual: 8 MiB de plaintext por registro. Cuota inicial tenant: 1 GiB de revisiones exteriores retenidas, 100000 revisiones, 120 requests/minuto; son límites técnicos MVP, no condiciones comerciales. - Operaciones aceptan `AbortSignal`; cancelar o sufrir timeout no demuestra que el servidor no haya confirmado una escritura. `VaultApiError` ofrece `status`, `code` y `correlationId`. 401 exige nueva autenticación; 404 no distingue recurso inexistente de falta de permiso; 409 conflicto/idempotencia; 412 concurrencia; 413 tamaño; 422 formato; 428 If-Match ausente; 429 cuota; 503 dependencia. No registrar tokens o contenido al diagnosticar errores. ## Seguridad y límites Navegadores modernos con Web Crypto y HTTPS; TypeScript no sustituye validación server. El cliente browser rechaza claves `svt.*`; la entrada `/server` rechaza ejecución con `window`. Esto ayuda a detectar errores, pero no protege un bundle donde alguien incruste deliberadamente secretos. No hay promesa de borrado seguro de memoria JS ni revocación de material ya descargado. Comprometer un servicio/dispositivo puede afectar varias participaciones, aunque cada participación aislada pertenezca a una revisión. Las primitivas y la separación de factores necesitan revisión criptográfica independiente antes de uso comercial sensible. Software propietario; distribución no equivale a licencia open source.