@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,104 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ /**
3
+ * Secret status values
4
+ */
5
+ export type SecretStatus = 'active' | 'disabled' | 'expired';
6
+ /**
7
+ * Secret represents an encrypted value stored per-tenant.
8
+ *
9
+ * Secrets are tenant-scoped and use envelope encryption:
10
+ * - Each tenant has their own Data Encryption Key (TDEK)
11
+ * - The TDEK is wrapped by the Application Master Key (AMK)
12
+ * - Secret values are encrypted by the unwrapped TDEK
13
+ *
14
+ * **Security**: This model deliberately excludes API and MCP exposure
15
+ * to prevent accidental secret leakage. Secrets are only accessible
16
+ * via CLI commands or direct service calls.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { SecretService } from '@happyvertical/smrt-secrets';
21
+ *
22
+ * const service = await SecretService.create({ db });
23
+ *
24
+ * await withTenant({ tenantId: 'tenant-123' }, async () => {
25
+ * // Store a secret
26
+ * await service.store('api-key', 'sk_live_xxx', { category: 'stripe' });
27
+ *
28
+ * // Retrieve (auto-decrypts)
29
+ * const apiKey = await service.retrieve('api-key');
30
+ * });
31
+ * ```
32
+ */
33
+ export declare class Secret extends SmrtObject {
34
+ /**
35
+ * Tenant that owns this secret. Also stored in context for per-tenant name uniqueness.
36
+ */
37
+ tenantId: string;
38
+ /**
39
+ * Unique name for the secret within the tenant
40
+ */
41
+ name: string;
42
+ /**
43
+ * Human-readable description
44
+ */
45
+ description: string;
46
+ /**
47
+ * Category for organization (e.g., 'database', 'api-key', 'oauth')
48
+ */
49
+ category: string;
50
+ /**
51
+ * JSON-encoded EncryptedEnvelope from @happyvertical/secrets
52
+ */
53
+ encryptedValue: string;
54
+ /**
55
+ * Version of the tenant key used to encrypt this secret
56
+ */
57
+ keyVersion: number;
58
+ /**
59
+ * Current status of the secret
60
+ */
61
+ status: SecretStatus;
62
+ /**
63
+ * Optional expiration date
64
+ */
65
+ expiresAt: Date | null;
66
+ /**
67
+ * Last time this secret was accessed (decrypted)
68
+ */
69
+ lastAccessedAt: Date | null;
70
+ /**
71
+ * Number of times this secret has been accessed
72
+ */
73
+ accessCount: number;
74
+ /**
75
+ * Additional metadata stored with the secret
76
+ */
77
+ metadata: Record<string, unknown>;
78
+ constructor(options?: any);
79
+ /**
80
+ * Check if the secret is currently active
81
+ */
82
+ isActive(): boolean;
83
+ /**
84
+ * Check if the secret has expired
85
+ */
86
+ isExpired(): boolean;
87
+ /**
88
+ * Check if the secret can be used (active and not expired)
89
+ */
90
+ isUsable(): boolean;
91
+ /**
92
+ * Record an access to this secret
93
+ */
94
+ recordAccess(): void;
95
+ /**
96
+ * Disable the secret
97
+ */
98
+ disable(): void;
99
+ /**
100
+ * Enable the secret
101
+ */
102
+ enable(): void;
103
+ }
104
+ //# sourceMappingURL=Secret.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Secret.d.ts","sourceRoot":"","sources":["../../src/models/Secret.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAE5D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAWH,qBASa,MAAO,SAAQ,UAAU;IACpC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAM;IAEtB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAM;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAM;IAEzB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAM;IAEtB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAM;IAE5B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAK;IAEvB;;OAEG;IACH,MAAM,EAAE,YAAY,CAAY;IAEhC;;OAEG;IACH,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;IAE9B;;OAEG;IACH,cAAc,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEnC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAK;IAExB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;gBAE3B,OAAO,GAAE,GAAQ;IAgC7B;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,SAAS,IAAI,OAAO;IAKpB;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,YAAY,IAAI,IAAI;IAKpB;;OAEG;IACH,OAAO,IAAI,IAAI;IAIf;;OAEG;IACH,MAAM,IAAI,IAAI;CAGf"}
@@ -0,0 +1,123 @@
1
+ import { SmrtCreateInput, SmrtObject } from '@happyvertical/smrt-core';
2
+ /**
3
+ * Secret audit action types
4
+ */
5
+ export type SecretAuditAction = 'create' | 'read' | 'update' | 'delete' | 'rotate_key' | 'disable' | 'enable' | 'expire';
6
+ /**
7
+ * Audit result types
8
+ */
9
+ export type SecretAuditResult = 'success' | 'failure' | 'denied';
10
+ /**
11
+ * SecretAuditLog records all operations on secrets for compliance
12
+ * and security monitoring.
13
+ *
14
+ * Every secret operation (create, read, update, delete, key rotation)
15
+ * is logged with the user, action, result, and relevant details.
16
+ *
17
+ * **Retention**: Audit logs should be retained according to your
18
+ * compliance requirements (typically 1-7 years).
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Query recent audit logs
23
+ * const logs = await auditLogs.list({
24
+ * where: { tenantId: 'tenant-123' },
25
+ * orderBy: 'created_at DESC',
26
+ * limit: 100
27
+ * });
28
+ *
29
+ * // Filter by action
30
+ * const reads = await auditLogs.list({
31
+ * where: {
32
+ * tenantId: 'tenant-123',
33
+ * action: 'read'
34
+ * }
35
+ * });
36
+ *
37
+ * // Filter by secret name
38
+ * const apiKeyLogs = await auditLogs.list({
39
+ * where: {
40
+ * tenantId: 'tenant-123',
41
+ * secretName: 'stripe-api-key'
42
+ * }
43
+ * });
44
+ * ```
45
+ */
46
+ export declare class SecretAuditLog extends SmrtObject {
47
+ /**
48
+ * Tenant associated with the audited secret operation.
49
+ */
50
+ tenantId: string | null;
51
+ /**
52
+ * ID of the secret (may be null for deleted secrets)
53
+ */
54
+ secretId: string | null;
55
+ /**
56
+ * Name of the secret at the time of the operation
57
+ */
58
+ secretName: string;
59
+ /**
60
+ * ID of the user who performed the action
61
+ */
62
+ userId: string;
63
+ /**
64
+ * The action that was performed
65
+ */
66
+ action: SecretAuditAction;
67
+ /**
68
+ * Result of the operation
69
+ */
70
+ result: SecretAuditResult;
71
+ /**
72
+ * IP address of the client (if available)
73
+ */
74
+ ipAddress: string;
75
+ /**
76
+ * User agent string (if available)
77
+ */
78
+ userAgent: string;
79
+ /**
80
+ * Additional context about the operation
81
+ */
82
+ details: Record<string, unknown>;
83
+ constructor(options?: any);
84
+ /**
85
+ * Check if this was a successful operation
86
+ */
87
+ isSuccess(): boolean;
88
+ /**
89
+ * Check if this was a failed operation
90
+ */
91
+ isFailure(): boolean;
92
+ /**
93
+ * Check if this was a denied operation (permission denied)
94
+ */
95
+ isDenied(): boolean;
96
+ /**
97
+ * Check if this is a read operation
98
+ */
99
+ isReadAction(): boolean;
100
+ /**
101
+ * Check if this is a write operation (create, update, delete)
102
+ */
103
+ isWriteAction(): boolean;
104
+ /**
105
+ * Check if this is a key operation
106
+ */
107
+ isKeyOperation(): boolean;
108
+ }
109
+ /**
110
+ * Create an audit log entry for a secret operation
111
+ */
112
+ export declare function createAuditEntry(params: {
113
+ secretId?: string | null;
114
+ secretName: string;
115
+ tenantId?: string | null;
116
+ userId: string;
117
+ action: SecretAuditAction;
118
+ result: SecretAuditResult;
119
+ ipAddress?: string;
120
+ userAgent?: string;
121
+ details?: Record<string, unknown>;
122
+ }): SmrtCreateInput<SecretAuditLog>;
123
+ //# sourceMappingURL=SecretAuditLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecretAuditLog.d.ts","sourceRoot":"","sources":["../../src/models/SecretAuditLog.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAGL,UAAU,EAEX,MAAM,0BAA0B,CAAC;AAElC;;GAEG;AACH,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,YAAY,GACZ,SAAS,GACT,QAAQ,GACR,QAAQ,CAAC;AAEb;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAUH,qBAQa,cAAe,SAAQ,UAAU;IAC5C;;OAEG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IAEH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAQ;IAE/B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IAEH,MAAM,EAAE,MAAM,CAAM;IAEpB;;OAEG;IACH,MAAM,EAAE,iBAAiB,CAAU;IAEnC;;OAEG;IACH,MAAM,EAAE,iBAAiB,CAAa;IAEtC;;OAEG;IACH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAM;IAEvB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM;gBAE1B,OAAO,GAAE,GAAQ;IAe7B;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;OAEG;IACH,cAAc,IAAI,OAAO;CAG1B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,iBAAiB,CAAC;IAC1B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,GAAG,eAAe,CAAC,cAAc,CAAC,CAkBlC"}
@@ -0,0 +1,101 @@
1
+ import { SmrtObject } from '@happyvertical/smrt-core';
2
+ /**
3
+ * Key status values
4
+ */
5
+ export type TenantKeyStatus = 'active' | 'rotating' | 'retired' | 'compromised';
6
+ /**
7
+ * TenantKey tracks the per-tenant Data Encryption Keys (TDEKs).
8
+ *
9
+ * Each tenant has one or more TDEKs stored in wrapped form. The wrapped key
10
+ * can only be decrypted using the Application Master Key (AMK).
11
+ *
12
+ * **Key Lifecycle**:
13
+ * 1. `active` - Current key used for encryption
14
+ * 2. `rotating` - Transitional state during rotation
15
+ * 3. `retired` - Old key kept for decryption of existing secrets
16
+ * 4. `compromised` - Key marked as compromised, should not be used
17
+ *
18
+ * **Note**: This model is NOT tenant-scoped itself because it tracks
19
+ * keys FOR tenants, not secrets owned BY tenants.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // Get active key for a tenant
24
+ * const key = await tenantKeys.get({
25
+ * tenantId: 'tenant-123',
26
+ * status: 'active'
27
+ * });
28
+ *
29
+ * // List all key versions for a tenant
30
+ * const versions = await tenantKeys.list({
31
+ * where: { tenantId: 'tenant-123' },
32
+ * orderBy: 'version DESC'
33
+ * });
34
+ * ```
35
+ */
36
+ export declare class TenantKey extends SmrtObject {
37
+ /**
38
+ * Tenant ID this key belongs to
39
+ */
40
+ tenantId: string;
41
+ /**
42
+ * Wrapped key data (format: wrappedKey:iv:authTag)
43
+ */
44
+ wrappedKey: string;
45
+ /**
46
+ * ID of the AMK used to wrap this key
47
+ */
48
+ amkKeyId: string;
49
+ /**
50
+ * Current status of the key
51
+ */
52
+ status: TenantKeyStatus;
53
+ /**
54
+ * Version number (increments on rotation)
55
+ */
56
+ version: number;
57
+ /**
58
+ * Recommended rotation date
59
+ */
60
+ rotateAfter: Date | null;
61
+ /**
62
+ * When the key was retired (if applicable)
63
+ */
64
+ retiredAt: Date | null;
65
+ constructor(options?: any);
66
+ /**
67
+ * Check if this key is currently active
68
+ */
69
+ isActive(): boolean;
70
+ /**
71
+ * Check if this key needs rotation
72
+ */
73
+ needsRotation(): boolean;
74
+ /**
75
+ * Check if this key is retired
76
+ */
77
+ isRetired(): boolean;
78
+ /**
79
+ * Check if this key is compromised
80
+ */
81
+ isCompromised(): boolean;
82
+ /**
83
+ * Check if this key can be used for decryption
84
+ * (active or retired keys can decrypt)
85
+ */
86
+ canDecrypt(): boolean;
87
+ /**
88
+ * Check if this key can be used for encryption
89
+ * (only active keys should encrypt)
90
+ */
91
+ canEncrypt(): boolean;
92
+ /**
93
+ * Mark this key as retired
94
+ */
95
+ retire(): void;
96
+ /**
97
+ * Mark this key as compromised
98
+ */
99
+ markCompromised(): void;
100
+ }
101
+ //# sourceMappingURL=TenantKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantKey.d.ts","sourceRoot":"","sources":["../../src/models/TenantKey.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAQ,MAAM,0BAA0B,CAAC;AAE5D;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;AAEhF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH,qBAQa,SAAU,SAAQ,UAAU;IACvC;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAM;IAEtB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAM;IAExB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAM;IAEtB;;OAEG;IACH,MAAM,EAAE,eAAe,CAAY;IAEnC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAK;IAEpB;;OAEG;IACH,WAAW,EAAE,IAAI,GAAG,IAAI,CAAQ;IAEhC;;OAEG;IACH,SAAS,EAAE,IAAI,GAAG,IAAI,CAAQ;gBAElB,OAAO,GAAE,GAAQ;IAyB7B;;OAEG;IACH,QAAQ,IAAI,OAAO;IAInB;;OAEG;IACH,aAAa,IAAI,OAAO;IAKxB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;;OAGG;IACH,UAAU,IAAI,OAAO;IAIrB;;;OAGG;IACH,UAAU,IAAI,OAAO;IAIrB;;OAEG;IACH,MAAM,IAAI,IAAI;IAKd;;OAEG;IACH,eAAe,IAAI,IAAI;CAGxB"}
@@ -0,0 +1,4 @@
1
+ export { Secret, type SecretStatus } from './Secret.js';
2
+ export { createAuditEntry, type SecretAuditAction, SecretAuditLog, type SecretAuditResult, } from './SecretAuditLog.js';
3
+ export { TenantKey, type TenantKeyStatus } from './TenantKey.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/models/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,yBAAyB,CAAC;AAEjC,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EACL,gBAAgB,EAChB,KAAK,iBAAiB,EACtB,cAAc,EACd,KAAK,iBAAiB,GACvB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { S, a, T, c } from "../chunks/TenantKey-DzglnpAV.js";
2
+ export {
3
+ S as Secret,
4
+ a as SecretAuditLog,
5
+ T as TenantKey,
6
+ c as createAuditEntry
7
+ };
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,266 @@
1
+ import { DatabaseInterface } from '@happyvertical/sql';
2
+ import { Secret } from '../models/Secret.js';
3
+ import { SecretAuditLog } from '../models/SecretAuditLog.js';
4
+ /**
5
+ * Options for creating a SecretService
6
+ */
7
+ export interface SecretServiceOptions {
8
+ /** Database connection */
9
+ db: DatabaseInterface;
10
+ /** Environment variable containing the AMK (64 hex chars) */
11
+ amkEnvVar?: string;
12
+ /** AMK key identifier */
13
+ amkKeyId?: string;
14
+ /** Enable audit logging (default: true) */
15
+ auditEnabled?: boolean;
16
+ }
17
+ /**
18
+ * Options for storing a secret
19
+ */
20
+ export interface StoreSecretOptions {
21
+ /** Human-readable description */
22
+ description?: string;
23
+ /** Category for organization */
24
+ category?: string;
25
+ /** Optional expiration date */
26
+ expiresAt?: Date;
27
+ /** Additional metadata */
28
+ metadata?: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * Result of retrieving a secret
32
+ */
33
+ export interface RetrievedSecret {
34
+ /** The decrypted secret value */
35
+ value: string;
36
+ /** Secret metadata */
37
+ name: string;
38
+ description: string;
39
+ category: string;
40
+ expiresAt: Date | null;
41
+ createdAt: Date;
42
+ lastAccessedAt: Date | null;
43
+ accessCount: number;
44
+ metadata: Record<string, unknown>;
45
+ }
46
+ export type SecretKeyDriftIssueSeverity = 'info' | 'warning' | 'error';
47
+ export type SecretKeyDriftIssueCode = 'amk_unavailable' | 'active_secrets_without_usable_active_key' | 'missing_active_tenant_encryption_key' | 'multiple_active_tenant_encryption_keys' | 'active_tenant_encryption_key_amk_mismatch' | 'active_tenant_encryption_key_unwrap_failed' | 'secret_envelope_invalid_json' | 'secret_envelope_invalid_wrapped_key' | 'secret_envelope_missing_tenant_encryption_key' | 'secret_envelope_unwrap_failed' | 'smrt_tenant_keys_query_failed' | 'smrt_tenant_keys_not_mirrored';
48
+ export type SecretKeyDriftRepairAction = 'delete-unrecoverable-secret' | 'delete-unusable-tenant-encryption-key' | 'store-fresh-secret-value' | 'none';
49
+ export interface SecretKeyDriftIssue {
50
+ code: SecretKeyDriftIssueCode;
51
+ severity: SecretKeyDriftIssueSeverity;
52
+ message: string;
53
+ repairAction: SecretKeyDriftRepairAction;
54
+ secretId?: string;
55
+ secretName?: string;
56
+ keyId?: string;
57
+ sourceTable?: 'secrets' | 'tenant_encryption_keys' | 'tenant_keys';
58
+ details?: Record<string, string | number | boolean | null>;
59
+ }
60
+ export interface DiagnoseTenantSecretKeyDriftOptions {
61
+ /**
62
+ * Limit secret-envelope checks to these names. Tenant key checks still run.
63
+ */
64
+ secretNames?: string[];
65
+ }
66
+ export interface SecretKeyDriftReport {
67
+ tenantId: string;
68
+ checkedAt: Date;
69
+ ok: boolean;
70
+ summary: {
71
+ activeSecretCount: number;
72
+ tenantEncryptionKeyCount: number;
73
+ activeTenantEncryptionKeyCount: number;
74
+ usableActiveTenantEncryptionKeyCount: number;
75
+ smrtTenantKeyCount: number;
76
+ activeSmrtTenantKeyCount: number;
77
+ };
78
+ issues: SecretKeyDriftIssue[];
79
+ }
80
+ export interface RepairTenantSecretKeyDriftOptions extends DiagnoseTenantSecretKeyDriftOptions {
81
+ /**
82
+ * Preview affected rows without deleting anything.
83
+ */
84
+ dryRun?: boolean;
85
+ /**
86
+ * Required for destructive repair. This deletes encrypted values/key rows
87
+ * that cannot be used with the currently configured AMK.
88
+ */
89
+ confirmDeleteUnrecoverableData?: boolean;
90
+ }
91
+ export interface SecretKeyDriftRepairResult {
92
+ tenantId: string;
93
+ dryRun: boolean;
94
+ issuesBefore: SecretKeyDriftIssue[];
95
+ remainingIssues: SecretKeyDriftIssue[];
96
+ wouldDeleteSecrets: number;
97
+ wouldDeleteTenantEncryptionKeys: number;
98
+ deletedSecrets: number;
99
+ deletedTenantEncryptionKeys: number;
100
+ secretNames: string[];
101
+ tenantEncryptionKeyIds: string[];
102
+ }
103
+ export declare class SecretKeyDriftError extends Error {
104
+ readonly code = "SECRET_KEY_DRIFT";
105
+ readonly tenantId: string;
106
+ readonly report: SecretKeyDriftReport;
107
+ readonly cause?: Error;
108
+ constructor(message: string, tenantId: string, report: SecretKeyDriftReport, cause?: Error);
109
+ }
110
+ /**
111
+ * SecretService provides high-level operations for managing per-tenant secrets.
112
+ *
113
+ * It integrates with:
114
+ * - `@happyvertical/secrets` for envelope encryption
115
+ * - `@happyvertical/smrt-tenancy` for tenant context
116
+ * - Audit logging for compliance
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * import { SecretService } from '@happyvertical/smrt-secrets';
121
+ * import { withTenant } from '@happyvertical/smrt-tenancy';
122
+ *
123
+ * const service = await SecretService.create({ db });
124
+ *
125
+ * await withTenant({ tenantId: 'tenant-123' }, async () => {
126
+ * // Store a secret
127
+ * await service.store('stripe-api-key', 'sk_live_xxx', {
128
+ * category: 'api-keys',
129
+ * description: 'Stripe production API key'
130
+ * });
131
+ *
132
+ * // Retrieve the secret
133
+ * const secret = await service.retrieve('stripe-api-key');
134
+ * console.log(secret.value); // 'sk_live_xxx'
135
+ *
136
+ * // List secret names (without values)
137
+ * const secrets = await service.list();
138
+ *
139
+ * // Rotate tenant's encryption key
140
+ * await service.rotateKey();
141
+ *
142
+ * // Delete a secret
143
+ * await service.delete('stripe-api-key');
144
+ * });
145
+ * ```
146
+ */
147
+ export declare class SecretService {
148
+ private db;
149
+ private secretStore;
150
+ private secrets;
151
+ private tenantKeys;
152
+ private auditLogs;
153
+ private auditEnabled;
154
+ private amkEnvVar;
155
+ private amkKeyId;
156
+ private constructor();
157
+ /**
158
+ * Create a new SecretService instance
159
+ */
160
+ static create(options: SecretServiceOptions): Promise<SecretService>;
161
+ /**
162
+ * Store a secret for the current tenant
163
+ */
164
+ store(name: string, value: string, options?: StoreSecretOptions): Promise<Secret>;
165
+ /**
166
+ * Store a secret for a specific tenant.
167
+ *
168
+ * This is useful for integrations that already resolved tenant ownership but
169
+ * may be running outside the application's ambient tenant context.
170
+ */
171
+ storeForTenant(tenantId: string, name: string, value: string, options?: StoreSecretOptions): Promise<Secret>;
172
+ /**
173
+ * Retrieve a secret for the current tenant
174
+ */
175
+ retrieve(name: string): Promise<RetrievedSecret>;
176
+ /**
177
+ * Retrieve a secret for a specific tenant.
178
+ */
179
+ retrieveForTenant(tenantId: string, name: string): Promise<RetrievedSecret>;
180
+ /**
181
+ * Diagnose tenant secret/key drift without exposing decrypted values.
182
+ */
183
+ diagnoseTenantSecretKeyDrift(tenantId: string, options?: DiagnoseTenantSecretKeyDriftOptions): Promise<SecretKeyDriftReport>;
184
+ /**
185
+ * Diagnose drift for the current tenant context.
186
+ */
187
+ diagnoseCurrentTenantSecretKeyDrift(options?: DiagnoseTenantSecretKeyDriftOptions): Promise<SecretKeyDriftReport>;
188
+ /**
189
+ * Delete unrecoverable secret/key rows identified by diagnosis.
190
+ *
191
+ * This never attempts to recover or expose secret values. Use dryRun first
192
+ * to preview destructive changes.
193
+ */
194
+ repairTenantSecretKeyDrift(tenantId: string, options?: RepairTenantSecretKeyDriftOptions): Promise<SecretKeyDriftRepairResult>;
195
+ /**
196
+ * List secrets for the current tenant (names only, not values)
197
+ */
198
+ list(options?: {
199
+ category?: string;
200
+ }): Promise<Secret[]>;
201
+ /**
202
+ * Delete a secret
203
+ */
204
+ delete(name: string): Promise<boolean>;
205
+ /**
206
+ * Disable a secret (soft delete)
207
+ */
208
+ disable(name: string): Promise<boolean>;
209
+ /**
210
+ * Enable a disabled secret
211
+ */
212
+ enable(name: string): Promise<boolean>;
213
+ /**
214
+ * Rotate the tenant's encryption key
215
+ *
216
+ * This creates a new TDEK and marks the old one as retired.
217
+ * Existing secrets remain encrypted with the old key and can still
218
+ * be decrypted (the old key is kept in retired state).
219
+ *
220
+ * For full re-encryption, call reencryptAll() after rotation.
221
+ */
222
+ rotateKey(): Promise<void>;
223
+ /**
224
+ * Re-encrypt all secrets with the current active key
225
+ *
226
+ * Call this after key rotation to ensure all secrets use the new key.
227
+ * This is optional but recommended for security.
228
+ */
229
+ reencryptAll(): Promise<{
230
+ success: number;
231
+ failed: number;
232
+ }>;
233
+ /**
234
+ * Get audit logs for the current tenant
235
+ */
236
+ getAuditLogs(options?: {
237
+ secretName?: string;
238
+ limit?: number;
239
+ }): Promise<SecretAuditLog[]>;
240
+ /**
241
+ * Get secret categories for the current tenant
242
+ */
243
+ getCategories(): Promise<string[]>;
244
+ /**
245
+ * Check if a secret exists for the current tenant
246
+ */
247
+ exists(name: string): Promise<boolean>;
248
+ private listActiveSecretRowsForDiagnosis;
249
+ private listTenantEncryptionKeyRows;
250
+ private listSmrtTenantKeysForDiagnosis;
251
+ private getConfiguredAmkForDiagnosis;
252
+ private checkWrappedKey;
253
+ private getWrappedKeyFingerprint;
254
+ private parseSecretEnvelopeForDiagnosis;
255
+ private deleteRowsByIds;
256
+ private auditSecretDriftRepairDeletes;
257
+ private rowsFromResult;
258
+ private classifyTenantKeyFailure;
259
+ private shouldClassifyTenantKeyFailure;
260
+ private getSecretErrorCode;
261
+ private toError;
262
+ private getCurrentUserId;
263
+ private audit;
264
+ private serializeMetadata;
265
+ }
266
+ //# sourceMappingURL=SecretService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecretService.d.ts","sourceRoot":"","sources":["../../src/services/SecretService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import "../chunks/TenantKey-DzglnpAV.js";
2
+ import { b, c } from "../chunks/SecretService-C91H6WJK.js";
3
+ import "@happyvertical/secrets";
4
+ import "@happyvertical/smrt-tenancy";
5
+ export {
6
+ b as SecretKeyDriftError,
7
+ c as SecretService
8
+ };
9
+ //# sourceMappingURL=SecretService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecretService.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}