@happyvertical/smrt-secrets 0.30.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.
Files changed (39) hide show
  1. package/AGENTS.md +66 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +144 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/chunks/SecretService-C91H6WJK.js +1275 -0
  8. package/dist/chunks/SecretService-C91H6WJK.js.map +1 -0
  9. package/dist/chunks/TenantKey-DzglnpAV.js +377 -0
  10. package/dist/chunks/TenantKey-DzglnpAV.js.map +1 -0
  11. package/dist/collections/SecretAuditLogCollection.d.ts +71 -0
  12. package/dist/collections/SecretAuditLogCollection.d.ts.map +1 -0
  13. package/dist/collections/SecretCollection.d.ts +63 -0
  14. package/dist/collections/SecretCollection.d.ts.map +1 -0
  15. package/dist/collections/TenantKeyCollection.d.ts +42 -0
  16. package/dist/collections/TenantKeyCollection.d.ts.map +1 -0
  17. package/dist/collections/index.d.ts +8 -0
  18. package/dist/collections/index.d.ts.map +1 -0
  19. package/dist/index.d.ts +12 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +26 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/manifest.json +1272 -0
  24. package/dist/models/Secret.d.ts +104 -0
  25. package/dist/models/Secret.d.ts.map +1 -0
  26. package/dist/models/SecretAuditLog.d.ts +123 -0
  27. package/dist/models/SecretAuditLog.d.ts.map +1 -0
  28. package/dist/models/TenantKey.d.ts +101 -0
  29. package/dist/models/TenantKey.d.ts.map +1 -0
  30. package/dist/models/index.d.ts +4 -0
  31. package/dist/models/index.d.ts.map +1 -0
  32. package/dist/models/index.js +8 -0
  33. package/dist/models/index.js.map +1 -0
  34. package/dist/services/SecretService.d.ts +266 -0
  35. package/dist/services/SecretService.d.ts.map +1 -0
  36. package/dist/services/SecretService.js +9 -0
  37. package/dist/services/SecretService.js.map +1 -0
  38. package/dist/smrt-knowledge.json +447 -0
  39. package/package.json +71 -0
package/AGENTS.md ADDED
@@ -0,0 +1,66 @@
1
+ # @happyvertical/smrt-secrets
2
+
3
+ Envelope encryption for per-tenant secret storage with key rotation and audit logging.
4
+
5
+ ## Encryption Architecture
6
+
7
+ ```
8
+ Secret value → encrypted with TDEK (per-tenant Data Encryption Key)
9
+ TDEK → wrapped with AMK (Application Master Key, from env SMRT_SECRET_MASTER_KEY)
10
+ ```
11
+
12
+ ## Models
13
+
14
+ - **Secret**: `encryptedValue` (JSON envelope), `category`, `status` (active/disabled/expired), `expiresAt`, `accessCount`, `lastAccessedAt`. **No API/MCP exposure** (security). CLI: list-only.
15
+ - **TenantKey**: per-tenant TDEK in wrapped form. Status: active/rotating/retired/compromised. **Not tenant-scoped itself** — it tracks keys FOR tenants.
16
+ - **SecretAuditLog**: action (create/read/update/delete/rotate_key/disable/enable), result (success/failure/denied), user/IP tracking. CLI: list-only.
17
+
18
+ ## SecretService
19
+
20
+ | Method | Behavior |
21
+ |--------|----------|
22
+ | `store(name, value, options)` | Encrypt + save. Upsert if exists. Uses `context=tenantId` for per-tenant uniqueness. |
23
+ | `retrieve(name)` | Decrypt + audit + increment accessCount |
24
+ | `diagnoseTenantSecretKeyDrift(tenantId)` | Report active secret/key drift without exposing values. Checks `secrets`, SDK `tenant_encryption_keys`, and SMRT `tenant_keys`. |
25
+ | `repairTenantSecretKeyDrift(tenantId, opts)` | Explicit repair path for unrecoverable drift. Use `dryRun` first; destructive cleanup requires `confirmDeleteUnrecoverableData: true`. |
26
+ | `rotateKey()` | Create new TDEK, mark old as retired |
27
+ | `reencryptAll()` | Decrypt with old TDEK, re-encrypt with new — **must call separately after rotateKey()** |
28
+ | `disable(name)` / `enable(name)` | Toggle status |
29
+
30
+ ## Gotchas
31
+
32
+ - **Key rotation doesn't auto-re-encrypt**: call `reencryptAll()` separately after `rotateKey()`
33
+ - **retrieve() increments accessCount**: every read is tracked
34
+ - **TenantKey NOT tenant-scoped**: it stores keys for tenants but isn't filtered by tenant context
35
+ - **Two key tables**: `tenant_encryption_keys` is the lower-level SDK table used by `SecretService` encryption; `tenant_keys` is the SMRT model table. Drift diagnosis reports both so an empty `tenant_keys` view is not confused with missing SDK key material.
36
+ - **Repair is destructive only by confirmation**: `repairTenantSecretKeyDrift()` deletes unrecoverable encrypted rows only when `confirmDeleteUnrecoverableData: true` is explicit. Do not silently discard encrypted secrets.
37
+ - **Expired secrets filtered by default**: pass `includeExpired: true` to list them
38
+ - **TenantKeyCollection.cleanupRetiredKeys()**: hard-deletes after 90 days
39
+ - **Audit logging optional but default**: failures logged to console, not thrown
40
+
41
+ ## Known exceptions to monorepo standards
42
+
43
+ Per `docs/content/standards.md §7`, tenant-aware models should normally apply
44
+ `@TenantScoped({ mode: 'optional' })` from `@happyvertical/smrt-tenancy`. The three
45
+ models in this package deviate intentionally; each `@smrt(...)` block carries an
46
+ inline comment pointing back to this section.
47
+
48
+ - **`Secret` (`src/models/Secret.ts`)** — uses the inline `tenantScoped: true` form
49
+ on `@smrt()` instead of the `@TenantScoped` decorator. `SecretService.store()`
50
+ performs manual scoping by populating `context = tenantId` on each row, so the
51
+ `(slug, context)` upsert key from the base `SmrtObject` is what isolates secret
52
+ names per tenant. Switching to the decorator without rethinking the upsert key
53
+ would surface false-positive name collisions across tenants.
54
+ - **`TenantKey` (`src/models/TenantKey.ts`)** — deliberately NOT tenant-scoped at
55
+ all. The row carries a `tenantId` column because each TDEK belongs to a tenant,
56
+ but key-rotation tooling, AMK rewrap jobs, and super-admin audits must query
57
+ across tenants; the tenancy interceptor would silently filter rows those flows
58
+ rely on.
59
+ - **`SecretAuditLog` (`src/models/SecretAuditLog.ts`)** — uses the inline
60
+ `tenantScoped: true` form rather than the decorator. Audit reads run in mixed
61
+ contexts (tenant-scoped reports vs. super-admin compliance review). Cross-
62
+ tenant audit queries should be wrapped in `withSuperAdminBypass()` from
63
+ `@happyvertical/smrt-tenancy` at the call site — there are no such cross-
64
+ tenant call sites in this package today, but consumers building compliance
65
+ tooling should adopt that pattern explicitly rather than relying on
66
+ decorator-implicit filtering.
package/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright <2025> <Happy Vertical Corporation>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # @happyvertical/smrt-secrets
2
+
3
+ Per-tenant secret management with envelope encryption for the SMRT framework. Uses a three-layer encryption chain: Application Master Key (AMK) wraps per-tenant Data Encryption Keys (TDEK), which encrypt individual secret values.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @happyvertical/smrt-secrets
9
+ ```
10
+
11
+ Requires the `SMRT_SECRET_MASTER_KEY` environment variable (64 hex characters) as the AMK.
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { SecretService } from '@happyvertical/smrt-secrets';
17
+ import { withTenant } from '@happyvertical/smrt-tenancy';
18
+
19
+ // Create the service (reads AMK from env by default)
20
+ const service = await SecretService.create({ db });
21
+
22
+ await withTenant({ tenantId: 'tenant-123' }, async () => {
23
+ // Store a secret (upserts if name already exists)
24
+ await service.store('stripe-api-key', 'sk_live_xxx', {
25
+ category: 'api-keys',
26
+ description: 'Stripe production key',
27
+ expiresAt: new Date('2027-01-01'),
28
+ });
29
+
30
+ // Retrieve and decrypt
31
+ const { value, accessCount } = await service.retrieve('stripe-api-key');
32
+
33
+ // List secret names (values never included)
34
+ const secrets = await service.list({ category: 'api-keys' });
35
+
36
+ // Disable/enable without deleting
37
+ await service.disable('stripe-api-key');
38
+ await service.enable('stripe-api-key');
39
+
40
+ // Rotate the tenant's encryption key
41
+ await service.rotateKey();
42
+ // Re-encrypt all secrets with the new key (separate step)
43
+ await service.reencryptAll();
44
+
45
+ // Query audit logs
46
+ const logs = await service.getAuditLogs({ secretName: 'stripe-api-key' });
47
+
48
+ // Diagnose tenant secret/key drift without exposing values
49
+ const drift = await service.diagnoseTenantSecretKeyDrift('tenant-123');
50
+
51
+ // Preview and then explicitly repair unrecoverable drifted rows
52
+ await service.repairTenantSecretKeyDrift('tenant-123', { dryRun: true });
53
+ await service.repairTenantSecretKeyDrift('tenant-123', {
54
+ confirmDeleteUnrecoverableData: true,
55
+ });
56
+
57
+ // Hard delete
58
+ await service.delete('stripe-api-key');
59
+ });
60
+ ```
61
+
62
+ ### Encryption chain
63
+
64
+ ```
65
+ AMK (env var, 256-bit)
66
+ └─ wraps TDEK (per-tenant, auto-generated)
67
+ └─ encrypts secret value (stored as JSON envelope)
68
+ ```
69
+
70
+ The AMK is loaded from `SMRT_SECRET_MASTER_KEY` (configurable via `amkEnvVar` option). Each tenant gets its own TDEK on first secret storage. TDEKs are stored in wrapped form -- they can only be unwrapped using the AMK. Secret values are encrypted by the unwrapped TDEK and persisted as `EncryptedEnvelope` JSON.
71
+
72
+ ### Key rotation
73
+
74
+ `rotateKey()` creates a new TDEK version and retires the old one. Existing secrets remain readable because retired keys are kept for decryption. Call `reencryptAll()` separately after rotation to re-encrypt all secrets with the new key. The method returns `{ success, failed }` counts.
75
+
76
+ TenantKey statuses: `active` (current encryption key), `rotating` (transitional), `retired` (kept for decryption), `compromised` (should not be used).
77
+
78
+ ### Drift diagnosis and repair
79
+
80
+ `diagnoseTenantSecretKeyDrift(tenantId)` reports tenant secret/key drift without
81
+ returning secret values. It checks active `secrets` rows, lower-level
82
+ `tenant_encryption_keys` rows used by `@happyvertical/secrets`, and SMRT-visible
83
+ `tenant_keys` rows so operators can distinguish missing secrets from stale or
84
+ unusable key material.
85
+
86
+ `repairTenantSecretKeyDrift(tenantId, { dryRun: true })` previews rows that would
87
+ be deleted. A destructive repair requires
88
+ `confirmDeleteUnrecoverableData: true`; it deletes only encrypted secret/key rows
89
+ identified as unrecoverable with the currently configured AMK. After repair,
90
+ store fresh secret values with `storeForTenant()` or `store()`.
91
+
92
+ ### Audit logging
93
+
94
+ Every secret operation (create, read, update, delete, rotate_key, disable, enable) is logged to `SecretAuditLog` with user ID, action, result (success/failure/denied), and optional IP/user-agent. Audit logging is enabled by default; failures are logged to console rather than thrown so they do not block secret operations.
95
+
96
+ ### Security model
97
+
98
+ The `Secret` and `TenantKey` models have no API or MCP exposure to prevent accidental leakage. CLI access is limited to listing names. The `TenantKey` model is deliberately not tenant-scoped -- it tracks keys FOR tenants rather than being owned BY them.
99
+
100
+ ## API
101
+
102
+ ### Models
103
+
104
+ | Export | Description |
105
+ |--------|------------|
106
+ | `Secret` | Encrypted secret record with status, expiration, and access tracking |
107
+ | `SecretAuditLog` | Immutable audit trail for all secret operations |
108
+ | `TenantKey` | Per-tenant data encryption key in wrapped form |
109
+
110
+ ### Collections
111
+
112
+ | Export | Description |
113
+ |--------|------------|
114
+ | `SecretCollection` | Secret queries with tenant filtering and category support |
115
+ | `SecretAuditLogCollection` | Audit log queries with filtering and pagination |
116
+ | `TenantKeyCollection` | Key management including retired key cleanup |
117
+
118
+ ### Services
119
+
120
+ | Export | Description |
121
+ |--------|------------|
122
+ | `SecretService` | High-level API: store, retrieve, rotate, delete, audit, diagnose and repair key drift |
123
+
124
+ ### Functions
125
+
126
+ | Export | Description |
127
+ |--------|------------|
128
+ | `createAuditEntry` | Build an audit log entry for a secret operation |
129
+
130
+ ### Error Classes (re-exported from `@happyvertical/secrets`)
131
+
132
+ `SecretError`, `AMKUnavailableError`, `DecryptionError`, `EncryptionError`, `InvalidKeyFormatError`, `KeyNotFoundError`, `KeyRotationError`, `StoreNotInitializedError`, `TenantKeyMissingError`
133
+
134
+ ### Key Types
135
+
136
+ `SecretServiceOptions`, `StoreSecretOptions`, `RetrievedSecret`, `DiagnoseTenantSecretKeyDriftOptions`, `SecretKeyDriftReport`, `SecretKeyDriftIssue`, `RepairTenantSecretKeyDriftOptions`, `SecretKeyDriftRepairResult`, `ListSecretsOptions`, `ListAuditLogsOptions`, `SecretStatus`, `SecretAuditAction`, `SecretAuditResult`, `TenantKeyStatus`, `ApplicationMasterKey`, `EncryptedEnvelope`, `TenantDataEncryptionKey`, `SecretStore`
137
+
138
+ ## Dependencies
139
+
140
+ - `@happyvertical/smrt-core` -- ORM and code generation
141
+ - `@happyvertical/smrt-tenancy` -- tenant context for scoped operations
142
+ - `@happyvertical/secrets` -- envelope encryption primitives
143
+ - `@happyvertical/sql` -- database interface
144
+ - `@happyvertical/utils` -- shared utilities
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__smrt-register__.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}