@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
@@ -0,0 +1,447 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "generatedAt": "2026-06-23T01:11:17.748Z",
4
+ "packageName": "@happyvertical/smrt-secrets",
5
+ "packageVersion": "0.30.0",
6
+ "sourceManifestPath": "dist/manifest.json",
7
+ "agentDocPath": "AGENTS.md",
8
+ "sourceHashes": {
9
+ "manifest": "1c7d7fa8c892b873aef663089c6e02f432669699afbd1d394adfa188207f5128",
10
+ "packageJson": "ed08c442d296f6603edfac12739a2477c84c3b37707431da2a56d4ec3548719e",
11
+ "agents": "c2be546ffbe79a0df95401c10a60e23f4f31cb8232cb1349fcdb7e5682852d4a"
12
+ },
13
+ "exports": [
14
+ ".",
15
+ "./manifest",
16
+ "./manifest.json",
17
+ "./models",
18
+ "./service"
19
+ ],
20
+ "dependencies": {
21
+ "@happyvertical/logger": "catalog:",
22
+ "@happyvertical/secrets": "catalog:",
23
+ "@happyvertical/smrt-core": "workspace:*",
24
+ "@happyvertical/smrt-tenancy": "workspace:*",
25
+ "@happyvertical/sql": "catalog:",
26
+ "@happyvertical/utils": "catalog:",
27
+ "@happyvertical/smrt-vitest": "workspace:*",
28
+ "@types/node": "25.0.9",
29
+ "typescript": "^5.9.3",
30
+ "vite": "^7.3.1",
31
+ "vitest": "^4.0.17"
32
+ },
33
+ "smrtDependencies": [
34
+ "@happyvertical/smrt-core",
35
+ "@happyvertical/smrt-tenancy",
36
+ "@happyvertical/smrt-vitest"
37
+ ],
38
+ "sdkDependencies": [
39
+ "@happyvertical/logger",
40
+ "@happyvertical/secrets",
41
+ "@happyvertical/sql",
42
+ "@happyvertical/utils"
43
+ ],
44
+ "tags": [],
45
+ "risks": [],
46
+ "objects": [
47
+ {
48
+ "name": "SecretAuditLogCollection",
49
+ "qualifiedName": "@happyvertical/smrt-secrets:SecretAuditLogCollection",
50
+ "collection": "secretauditlogs",
51
+ "tableName": "secret_audit_log_collections",
52
+ "packageName": "@happyvertical/smrt-secrets",
53
+ "extends": "SmrtCollection",
54
+ "fields": [],
55
+ "relationships": [],
56
+ "methods": [
57
+ "cleanup",
58
+ "countByAction",
59
+ "countByResult",
60
+ "getRecentDenials",
61
+ "getRecentFailures",
62
+ "getSecretHistory",
63
+ "getUserActivity",
64
+ "listLogs"
65
+ ],
66
+ "surfaces": [],
67
+ "relationshipFeatures": [
68
+ "uuidColumns"
69
+ ],
70
+ "tags": [],
71
+ "risks": []
72
+ },
73
+ {
74
+ "name": "SecretCollection",
75
+ "qualifiedName": "@happyvertical/smrt-secrets:SecretCollection",
76
+ "collection": "secrets",
77
+ "tableName": "secret_collections",
78
+ "packageName": "@happyvertical/smrt-secrets",
79
+ "extends": "SmrtCollection",
80
+ "fields": [],
81
+ "relationships": [],
82
+ "methods": [
83
+ "countByStatus",
84
+ "deleteByName",
85
+ "findByName",
86
+ "getCategories",
87
+ "listActive",
88
+ "listByCategory",
89
+ "listExpiring",
90
+ "listSecrets"
91
+ ],
92
+ "surfaces": [],
93
+ "relationshipFeatures": [
94
+ "uuidColumns"
95
+ ],
96
+ "tags": [],
97
+ "risks": []
98
+ },
99
+ {
100
+ "name": "TenantKeyCollection",
101
+ "qualifiedName": "@happyvertical/smrt-secrets:TenantKeyCollection",
102
+ "collection": "tenantkeys",
103
+ "tableName": "tenant_key_collections",
104
+ "packageName": "@happyvertical/smrt-secrets",
105
+ "extends": "SmrtCollection",
106
+ "fields": [],
107
+ "relationships": [],
108
+ "methods": [
109
+ "cleanupRetiredKeys",
110
+ "countByStatus",
111
+ "findKeysNeedingRotation",
112
+ "getActiveKey",
113
+ "getKeyVersion",
114
+ "listAllActiveKeys",
115
+ "listKeyVersions",
116
+ "markCompromised"
117
+ ],
118
+ "surfaces": [],
119
+ "relationshipFeatures": [
120
+ "uuidColumns"
121
+ ],
122
+ "tags": [],
123
+ "risks": []
124
+ },
125
+ {
126
+ "name": "Secret",
127
+ "qualifiedName": "@happyvertical/smrt-secrets:Secret",
128
+ "collection": "secrets",
129
+ "tableName": "secrets",
130
+ "packageName": "@happyvertical/smrt-secrets",
131
+ "extends": "SmrtObject",
132
+ "fields": [
133
+ {
134
+ "name": "tenantId",
135
+ "type": "text",
136
+ "required": false,
137
+ "columnType": "UUID"
138
+ },
139
+ {
140
+ "name": "name",
141
+ "type": "text",
142
+ "required": false,
143
+ "columnType": "TEXT"
144
+ },
145
+ {
146
+ "name": "description",
147
+ "type": "text",
148
+ "required": false,
149
+ "columnType": "TEXT"
150
+ },
151
+ {
152
+ "name": "category",
153
+ "type": "text",
154
+ "required": false,
155
+ "columnType": "TEXT"
156
+ },
157
+ {
158
+ "name": "encryptedValue",
159
+ "type": "text",
160
+ "required": false,
161
+ "columnType": "TEXT"
162
+ },
163
+ {
164
+ "name": "keyVersion",
165
+ "type": "integer",
166
+ "required": false,
167
+ "columnType": "INTEGER"
168
+ },
169
+ {
170
+ "name": "status",
171
+ "type": "text",
172
+ "required": false,
173
+ "columnType": "TEXT"
174
+ },
175
+ {
176
+ "name": "expiresAt",
177
+ "type": "datetime",
178
+ "required": false,
179
+ "columnType": "TIMESTAMP"
180
+ },
181
+ {
182
+ "name": "lastAccessedAt",
183
+ "type": "datetime",
184
+ "required": false,
185
+ "columnType": "TIMESTAMP"
186
+ },
187
+ {
188
+ "name": "accessCount",
189
+ "type": "integer",
190
+ "required": false,
191
+ "columnType": "INTEGER"
192
+ },
193
+ {
194
+ "name": "metadata",
195
+ "type": "json",
196
+ "required": false,
197
+ "columnType": "JSON"
198
+ }
199
+ ],
200
+ "relationships": [],
201
+ "methods": [
202
+ "disable",
203
+ "enable",
204
+ "isActive",
205
+ "isExpired",
206
+ "isUsable",
207
+ "recordAccess"
208
+ ],
209
+ "surfaces": [
210
+ {
211
+ "kind": "cli",
212
+ "name": "secret_list",
213
+ "operation": "list",
214
+ "objectName": "@happyvertical/smrt-secrets:Secret"
215
+ }
216
+ ],
217
+ "relationshipFeatures": [
218
+ "uuidColumns"
219
+ ],
220
+ "tags": [],
221
+ "risks": []
222
+ },
223
+ {
224
+ "name": "SecretAuditLog",
225
+ "qualifiedName": "@happyvertical/smrt-secrets:SecretAuditLog",
226
+ "collection": "secretauditlogs",
227
+ "tableName": "secret_audit_logs",
228
+ "packageName": "@happyvertical/smrt-secrets",
229
+ "extends": "SmrtObject",
230
+ "fields": [
231
+ {
232
+ "name": "tenantId",
233
+ "type": "text",
234
+ "required": false,
235
+ "columnType": "UUID"
236
+ },
237
+ {
238
+ "name": "secretId",
239
+ "type": "foreignKey",
240
+ "required": false,
241
+ "related": "Secret",
242
+ "columnType": "UUID"
243
+ },
244
+ {
245
+ "name": "secretName",
246
+ "type": "text",
247
+ "required": false,
248
+ "columnType": "TEXT"
249
+ },
250
+ {
251
+ "name": "userId",
252
+ "type": "crossPackageRef",
253
+ "required": false,
254
+ "related": "@happyvertical/smrt-users:User",
255
+ "columnType": "UUID"
256
+ },
257
+ {
258
+ "name": "action",
259
+ "type": "text",
260
+ "required": false,
261
+ "columnType": "TEXT"
262
+ },
263
+ {
264
+ "name": "result",
265
+ "type": "text",
266
+ "required": false,
267
+ "columnType": "TEXT"
268
+ },
269
+ {
270
+ "name": "ipAddress",
271
+ "type": "text",
272
+ "required": false,
273
+ "columnType": "TEXT"
274
+ },
275
+ {
276
+ "name": "userAgent",
277
+ "type": "text",
278
+ "required": false,
279
+ "columnType": "TEXT"
280
+ },
281
+ {
282
+ "name": "details",
283
+ "type": "json",
284
+ "required": false,
285
+ "columnType": "JSON"
286
+ }
287
+ ],
288
+ "relationships": [
289
+ {
290
+ "name": "secretId",
291
+ "type": "foreignKey",
292
+ "required": false,
293
+ "related": "Secret",
294
+ "columnType": "UUID"
295
+ },
296
+ {
297
+ "name": "userId",
298
+ "type": "crossPackageRef",
299
+ "required": false,
300
+ "related": "@happyvertical/smrt-users:User",
301
+ "columnType": "UUID"
302
+ }
303
+ ],
304
+ "methods": [
305
+ "isDenied",
306
+ "isFailure",
307
+ "isKeyOperation",
308
+ "isReadAction",
309
+ "isSuccess",
310
+ "isWriteAction"
311
+ ],
312
+ "surfaces": [
313
+ {
314
+ "kind": "cli",
315
+ "name": "secretauditlog_list",
316
+ "operation": "list",
317
+ "objectName": "@happyvertical/smrt-secrets:SecretAuditLog"
318
+ }
319
+ ],
320
+ "relationshipFeatures": [
321
+ "crossPackageRef",
322
+ "foreignKey",
323
+ "uuidColumns"
324
+ ],
325
+ "tags": [],
326
+ "risks": []
327
+ },
328
+ {
329
+ "name": "TenantKey",
330
+ "qualifiedName": "@happyvertical/smrt-secrets:TenantKey",
331
+ "collection": "tenantkeys",
332
+ "tableName": "tenant_keys",
333
+ "packageName": "@happyvertical/smrt-secrets",
334
+ "extends": "SmrtObject",
335
+ "fields": [
336
+ {
337
+ "name": "tenantId",
338
+ "type": "text",
339
+ "required": false,
340
+ "columnType": "TEXT"
341
+ },
342
+ {
343
+ "name": "wrappedKey",
344
+ "type": "text",
345
+ "required": false,
346
+ "columnType": "TEXT"
347
+ },
348
+ {
349
+ "name": "amkKeyId",
350
+ "type": "text",
351
+ "required": false,
352
+ "columnType": "TEXT"
353
+ },
354
+ {
355
+ "name": "status",
356
+ "type": "text",
357
+ "required": false,
358
+ "columnType": "TEXT"
359
+ },
360
+ {
361
+ "name": "version",
362
+ "type": "integer",
363
+ "required": false,
364
+ "columnType": "INTEGER"
365
+ },
366
+ {
367
+ "name": "rotateAfter",
368
+ "type": "datetime",
369
+ "required": false,
370
+ "columnType": "TIMESTAMP"
371
+ },
372
+ {
373
+ "name": "retiredAt",
374
+ "type": "datetime",
375
+ "required": false,
376
+ "columnType": "TIMESTAMP"
377
+ }
378
+ ],
379
+ "relationships": [],
380
+ "methods": [
381
+ "canDecrypt",
382
+ "canEncrypt",
383
+ "isActive",
384
+ "isCompromised",
385
+ "isRetired",
386
+ "markCompromised",
387
+ "needsRotation",
388
+ "retire"
389
+ ],
390
+ "surfaces": [
391
+ {
392
+ "kind": "cli",
393
+ "name": "tenantkey_list",
394
+ "operation": "list",
395
+ "objectName": "@happyvertical/smrt-secrets:TenantKey"
396
+ },
397
+ {
398
+ "kind": "cli",
399
+ "name": "tenantkey_get",
400
+ "operation": "get",
401
+ "objectName": "@happyvertical/smrt-secrets:TenantKey"
402
+ }
403
+ ],
404
+ "relationshipFeatures": [
405
+ "uuidColumns"
406
+ ],
407
+ "tags": [],
408
+ "risks": []
409
+ }
410
+ ],
411
+ "surfaces": [
412
+ {
413
+ "kind": "cli",
414
+ "name": "secret_list",
415
+ "operation": "list",
416
+ "objectName": "@happyvertical/smrt-secrets:Secret"
417
+ },
418
+ {
419
+ "kind": "cli",
420
+ "name": "secretauditlog_list",
421
+ "operation": "list",
422
+ "objectName": "@happyvertical/smrt-secrets:SecretAuditLog"
423
+ },
424
+ {
425
+ "kind": "cli",
426
+ "name": "tenantkey_list",
427
+ "operation": "list",
428
+ "objectName": "@happyvertical/smrt-secrets:TenantKey"
429
+ },
430
+ {
431
+ "kind": "cli",
432
+ "name": "tenantkey_get",
433
+ "operation": "get",
434
+ "objectName": "@happyvertical/smrt-secrets:TenantKey"
435
+ }
436
+ ],
437
+ "prompts": [],
438
+ "relationshipsV2": {
439
+ "foreignKeyFields": 1,
440
+ "crossPackageRefFields": 1,
441
+ "junctionCollections": 0,
442
+ "hierarchicalObjects": 0,
443
+ "polymorphicAssociations": 0,
444
+ "uuidColumns": 10
445
+ },
446
+ "agentDoc": "# @happyvertical/smrt-secrets\n\nEnvelope encryption for per-tenant secret storage with key rotation and audit logging.\n\n## Encryption Architecture\n\n```\nSecret value → encrypted with TDEK (per-tenant Data Encryption Key)\n TDEK → wrapped with AMK (Application Master Key, from env SMRT_SECRET_MASTER_KEY)\n```\n\n## Models\n\n- **Secret**: `encryptedValue` (JSON envelope), `category`, `status` (active/disabled/expired), `expiresAt`, `accessCount`, `lastAccessedAt`. **No API/MCP exposure** (security). CLI: list-only.\n- **TenantKey**: per-tenant TDEK in wrapped form. Status: active/rotating/retired/compromised. **Not tenant-scoped itself** — it tracks keys FOR tenants.\n- **SecretAuditLog**: action (create/read/update/delete/rotate_key/disable/enable), result (success/failure/denied), user/IP tracking. CLI: list-only.\n\n## SecretService\n\n| Method | Behavior |\n|--------|----------|\n| `store(name, value, options)` | Encrypt + save. Upsert if exists. Uses `context=tenantId` for per-tenant uniqueness. |\n| `retrieve(name)` | Decrypt + audit + increment accessCount |\n| `diagnoseTenantSecretKeyDrift(tenantId)` | Report active secret/key drift without exposing values. Checks `secrets`, SDK `tenant_encryption_keys`, and SMRT `tenant_keys`. |\n| `repairTenantSecretKeyDrift(tenantId, opts)` | Explicit repair path for unrecoverable drift. Use `dryRun` first; destructive cleanup requires `confirmDeleteUnrecoverableData: true`. |\n| `rotateKey()` | Create new TDEK, mark old as retired |\n| `reencryptAll()` | Decrypt with old TDEK, re-encrypt with new — **must call separately after rotateKey()** |\n| `disable(name)` / `enable(name)` | Toggle status |\n\n## Gotchas\n\n- **Key rotation doesn't auto-re-encrypt**: call `reencryptAll()` separately after `rotateKey()`\n- **retrieve() increments accessCount**: every read is tracked\n- **TenantKey NOT tenant-scoped**: it stores keys for tenants but isn't filtered by tenant context\n- **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.\n- **Repair is destructive only by confirmation**: `repairTenantSecretKeyDrift()` deletes unrecoverable encrypted rows only when `confirmDeleteUnrecoverableData: true` is explicit. Do not silently discard encrypted secrets.\n- **Expired secrets filtered by default**: pass `includeExpired: true` to list them\n- **TenantKeyCollection.cleanupRetiredKeys()**: hard-deletes after 90 days\n- **Audit logging optional but default**: failures logged to console, not thrown\n\n## Known exceptions to monorepo standards\n\nPer `docs/content/standards.md §7`, tenant-aware models should normally apply\n`@TenantScoped({ mode: 'optional' })` from `@happyvertical/smrt-tenancy`. The three\nmodels in this package deviate intentionally; each `@smrt(...)` block carries an\ninline comment pointing back to this section.\n\n- **`Secret` (`src/models/Secret.ts`)** — uses the inline `tenantScoped: true` form\n on `@smrt()` instead of the `@TenantScoped` decorator. `SecretService.store()`\n performs manual scoping by populating `context = tenantId` on each row, so the\n `(slug, context)` upsert key from the base `SmrtObject` is what isolates secret\n names per tenant. Switching to the decorator without rethinking the upsert key\n would surface false-positive name collisions across tenants.\n- **`TenantKey` (`src/models/TenantKey.ts`)** — deliberately NOT tenant-scoped at\n all. The row carries a `tenantId` column because each TDEK belongs to a tenant,\n but key-rotation tooling, AMK rewrap jobs, and super-admin audits must query\n across tenants; the tenancy interceptor would silently filter rows those flows\n rely on.\n- **`SecretAuditLog` (`src/models/SecretAuditLog.ts`)** — uses the inline\n `tenantScoped: true` form rather than the decorator. Audit reads run in mixed\n contexts (tenant-scoped reports vs. super-admin compliance review). Cross-\n tenant audit queries should be wrapped in `withSuperAdminBypass()` from\n `@happyvertical/smrt-tenancy` at the call site — there are no such cross-\n tenant call sites in this package today, but consumers building compliance\n tooling should adopt that pattern explicitly rather than relying on\n decorator-implicit filtering.\n"
447
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@happyvertical/smrt-secrets",
3
+ "version": "0.30.0",
4
+ "description": "Per-tenant secret management with envelope encryption for SMRT",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "CLAUDE.md",
11
+ "AGENTS.md"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./manifest": "./dist/manifest.json",
19
+ "./manifest.json": "./dist/manifest.json",
20
+ "./models": {
21
+ "types": "./dist/models/index.d.ts",
22
+ "import": "./dist/models/index.js"
23
+ },
24
+ "./service": {
25
+ "types": "./dist/services/SecretService.d.ts",
26
+ "import": "./dist/services/SecretService.js"
27
+ }
28
+ },
29
+ "dependencies": {
30
+ "@happyvertical/logger": "^0.74.7",
31
+ "@happyvertical/secrets": "^0.74.7",
32
+ "@happyvertical/sql": "^0.74.7",
33
+ "@happyvertical/utils": "^0.74.7",
34
+ "@happyvertical/smrt-core": "0.30.0",
35
+ "@happyvertical/smrt-tenancy": "0.30.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "25.0.9",
39
+ "typescript": "^5.9.3",
40
+ "vite": "^7.3.1",
41
+ "vitest": "^4.0.17",
42
+ "@happyvertical/smrt-vitest": "0.30.0"
43
+ },
44
+ "keywords": [
45
+ "smrt",
46
+ "secrets",
47
+ "encryption",
48
+ "envelope-encryption",
49
+ "multi-tenancy",
50
+ "ai"
51
+ ],
52
+ "author": "HappyVertical",
53
+ "license": "MIT",
54
+ "publishConfig": {
55
+ "registry": "https://registry.npmjs.org",
56
+ "access": "public"
57
+ },
58
+ "repository": {
59
+ "type": "git",
60
+ "url": "https://github.com/happyvertical/smrt.git",
61
+ "directory": "packages/secrets"
62
+ },
63
+ "scripts": {
64
+ "build": "vite build --mode library",
65
+ "build:watch": "vite build --mode library --watch",
66
+ "clean": "rm -rf dist",
67
+ "test": "vitest run",
68
+ "test:watch": "vitest",
69
+ "typecheck": "tsc --noEmit -p tsconfig.json"
70
+ }
71
+ }