@happyvertical/secrets 0.74.8
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/AGENT.md +33 -0
- package/LICENSE +7 -0
- package/README.md +220 -0
- package/dist/adapters/database.d.ts +74 -0
- package/dist/adapters/database.d.ts.map +1 -0
- package/dist/cli/claude-context.d.ts +3 -0
- package/dist/cli/claude-context.d.ts.map +1 -0
- package/dist/cli/claude-context.js +21 -0
- package/dist/cli/claude-context.js.map +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +736 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/envelope.d.ts +143 -0
- package/dist/shared/envelope.d.ts.map +1 -0
- package/dist/shared/errors.d.ts +61 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/factory.d.ts +44 -0
- package/dist/shared/factory.d.ts.map +1 -0
- package/dist/shared/types.d.ts +243 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/metadata.json +29 -0
- package/package.json +63 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @happyvertical/secrets
|
|
2
|
+
|
|
3
|
+
<!-- BEGIN AGENT:GENERATED -->
|
|
4
|
+
## Purpose
|
|
5
|
+
Envelope encryption for per-tenant secret management with pluggable backends
|
|
6
|
+
|
|
7
|
+
## Package Map
|
|
8
|
+
- Package: `@happyvertical/secrets`
|
|
9
|
+
- Hierarchy path: `@happyvertical/sdk > packages > secrets`
|
|
10
|
+
- Workspace position: `24 of 30` local packages
|
|
11
|
+
- Internal dependencies: `@happyvertical/sql`, `@happyvertical/utils`
|
|
12
|
+
- Internal dependents: none
|
|
13
|
+
- Knowledge graph files: `AGENT.md`, `metadata.json`, `ecosystem-manifest.json`
|
|
14
|
+
|
|
15
|
+
## Build & Test
|
|
16
|
+
```bash
|
|
17
|
+
pnpm --filter @happyvertical/secrets build
|
|
18
|
+
pnpm --filter @happyvertical/secrets test
|
|
19
|
+
pnpm --filter @happyvertical/secrets typecheck
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Agent Correction Loops
|
|
23
|
+
- If module resolution or export errors mention a workspace dependency, build the dependency first (`pnpm --filter @happyvertical/sql build`, `pnpm --filter @happyvertical/utils build`) and then rerun `pnpm --filter @happyvertical/secrets build`.
|
|
24
|
+
- If you hit type-only regressions, run `pnpm --filter @happyvertical/secrets typecheck` before rerunning the package build or tests to isolate the failing surface.
|
|
25
|
+
- If failures span multiple packages or Turborepo ordering looks wrong, run `pnpm build` and `pnpm typecheck` from the repo root before retrying package-scoped commands.
|
|
26
|
+
|
|
27
|
+
## Ecosystem Relationships
|
|
28
|
+
- Provides: Envelope encryption for per-tenant secret management with pluggable backends
|
|
29
|
+
- Implements: none
|
|
30
|
+
- Requires: @happyvertical/sql, @happyvertical/utils
|
|
31
|
+
- Stability: stable (Primary package surface is described as implemented and production-oriented.)
|
|
32
|
+
<!-- END AGENT:GENERATED -->
|
|
33
|
+
|
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,220 @@
|
|
|
1
|
+
# @happyvertical/secrets
|
|
2
|
+
|
|
3
|
+
Envelope encryption SDK for per-tenant secret management with pluggable backends.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @happyvertical/secrets
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @happyvertical/secrets
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Claude Code Context
|
|
14
|
+
|
|
15
|
+
Install Claude Code context files for AI-assisted development:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx have-secrets-context
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This copies the package's `AGENT.md` documentation and `metadata.json` metadata to your project's `.claude/` directory, enabling Claude to provide better assistance when working with this package.
|
|
22
|
+
|
|
23
|
+
## Overview
|
|
24
|
+
|
|
25
|
+
This package provides envelope encryption primitives for secure, per-tenant secret management. It uses a two-tier key hierarchy:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Application Master Key (AMK) - from environment variable
|
|
29
|
+
└── wraps → Tenant Data Encryption Keys (TDEKs) - per tenant, stored in DB
|
|
30
|
+
└── encrypts → Secret values (AES-256-GCM)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { getSecretStore } from '@happyvertical/secrets';
|
|
37
|
+
import { getDatabase } from '@happyvertical/sql';
|
|
38
|
+
|
|
39
|
+
// Set up database and AMK
|
|
40
|
+
const db = await getDatabase({ type: 'sqlite', url: ':memory:' });
|
|
41
|
+
process.env.MY_SECRET_KEY = crypto.randomBytes(32).toString('hex');
|
|
42
|
+
|
|
43
|
+
// Create secret store
|
|
44
|
+
const store = await getSecretStore({
|
|
45
|
+
type: 'database',
|
|
46
|
+
db,
|
|
47
|
+
amk: {
|
|
48
|
+
provider: 'env',
|
|
49
|
+
keyEnvVar: 'MY_SECRET_KEY',
|
|
50
|
+
keyId: 'amk-v1',
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Create tenant key
|
|
55
|
+
await store.createTenantKey('tenant-123');
|
|
56
|
+
|
|
57
|
+
// Encrypt a secret
|
|
58
|
+
const envelope = await store.encrypt('tenant-123', 'api-key', 'sk_live_xxx');
|
|
59
|
+
|
|
60
|
+
// Decrypt the secret
|
|
61
|
+
const { value } = await store.decrypt('tenant-123', envelope);
|
|
62
|
+
console.log(value); // 'sk_live_xxx'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Core Concepts
|
|
66
|
+
|
|
67
|
+
### Envelope Encryption
|
|
68
|
+
|
|
69
|
+
Envelope encryption separates data encryption from key management:
|
|
70
|
+
|
|
71
|
+
1. **Application Master Key (AMK)**: A 32-byte key stored securely (env var, KMS, etc.)
|
|
72
|
+
2. **Tenant Data Encryption Keys (TDEKs)**: Per-tenant keys wrapped by the AMK
|
|
73
|
+
3. **Secret Values**: Encrypted with tenant's TDEK using AES-256-GCM
|
|
74
|
+
|
|
75
|
+
This architecture enables:
|
|
76
|
+
- Per-tenant key isolation
|
|
77
|
+
- Key rotation without re-encrypting all secrets
|
|
78
|
+
- Secure key storage (only wrapped keys in database)
|
|
79
|
+
|
|
80
|
+
### SecretStore Interface
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
interface SecretStore {
|
|
84
|
+
// Encrypt a secret for a tenant
|
|
85
|
+
encrypt(tenantId: string, secretName: string, plaintext: string): Promise<EncryptedEnvelope>;
|
|
86
|
+
|
|
87
|
+
// Decrypt a secret
|
|
88
|
+
decrypt(tenantId: string, envelope: EncryptedEnvelope): Promise<DecryptedSecret>;
|
|
89
|
+
|
|
90
|
+
// Tenant key management
|
|
91
|
+
getTenantKey(tenantId: string): Promise<TenantDataEncryptionKey | null>;
|
|
92
|
+
createTenantKey(tenantId: string): Promise<TenantDataEncryptionKey>;
|
|
93
|
+
rotateTenantKey(tenantId: string): Promise<TenantDataEncryptionKey>;
|
|
94
|
+
|
|
95
|
+
// Event subscription
|
|
96
|
+
subscribe(listener: SecretStoreEventListener): Unsubscribe;
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## API Reference
|
|
101
|
+
|
|
102
|
+
### getSecretStore(options)
|
|
103
|
+
|
|
104
|
+
Factory function to create a secret store instance.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const store = await getSecretStore({
|
|
108
|
+
type: 'database',
|
|
109
|
+
db: databaseInstance,
|
|
110
|
+
keysTable: 'tenant_encryption_keys', // optional, default
|
|
111
|
+
amk: {
|
|
112
|
+
provider: 'env',
|
|
113
|
+
keyEnvVar: 'SMRT_SECRET_MASTER_KEY',
|
|
114
|
+
keyId: 'production-amk-v1',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### EnvelopeEncryption
|
|
120
|
+
|
|
121
|
+
Low-level encryption primitives:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { EnvelopeEncryption } from '@happyvertical/secrets';
|
|
125
|
+
|
|
126
|
+
// Generate a data key
|
|
127
|
+
const dataKey = EnvelopeEncryption.generateDataKey();
|
|
128
|
+
|
|
129
|
+
// Wrap the key with AMK
|
|
130
|
+
const wrapped = EnvelopeEncryption.wrapKey(dataKey, amk);
|
|
131
|
+
|
|
132
|
+
// Encrypt data
|
|
133
|
+
const encrypted = EnvelopeEncryption.encryptData('secret', dataKey);
|
|
134
|
+
|
|
135
|
+
// Decrypt data
|
|
136
|
+
const plaintext = EnvelopeEncryption.decryptData(
|
|
137
|
+
encrypted.ciphertext,
|
|
138
|
+
encrypted.iv,
|
|
139
|
+
encrypted.authTag,
|
|
140
|
+
dataKey,
|
|
141
|
+
);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Key Rotation
|
|
145
|
+
|
|
146
|
+
Rotate tenant keys without service interruption:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// Old keys are retained for decryption
|
|
150
|
+
const newKey = await store.rotateTenantKey('tenant-123');
|
|
151
|
+
|
|
152
|
+
// Old envelopes still decrypt (using retained key version)
|
|
153
|
+
const decrypted = await store.decrypt('tenant-123', oldEnvelope);
|
|
154
|
+
|
|
155
|
+
// New envelopes use the new key
|
|
156
|
+
const newEnvelope = await store.encrypt('tenant-123', 'new-secret', 'value');
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Events
|
|
160
|
+
|
|
161
|
+
Subscribe to encryption/decryption events:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
const unsubscribe = store.subscribe((event) => {
|
|
165
|
+
console.log(`${event.type} for tenant ${event.tenantId}`);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Later: unsubscribe
|
|
169
|
+
unsubscribe();
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Event types:
|
|
173
|
+
- `secret.encrypted` - Secret was encrypted
|
|
174
|
+
- `secret.decrypted` - Secret was decrypted
|
|
175
|
+
- `key.created` - Tenant key was created
|
|
176
|
+
- `key.rotated` - Tenant key was rotated
|
|
177
|
+
|
|
178
|
+
## Security Considerations
|
|
179
|
+
|
|
180
|
+
1. **AMK Protection**: Store the Application Master Key securely
|
|
181
|
+
- Use environment variables for simple deployments
|
|
182
|
+
- Use AWS KMS, HashiCorp Vault, or Azure Key Vault for production
|
|
183
|
+
|
|
184
|
+
2. **Key Isolation**: Each tenant has a unique TDEK
|
|
185
|
+
- Compromise of one tenant's data doesn't expose others
|
|
186
|
+
- Keys can be rotated independently
|
|
187
|
+
|
|
188
|
+
3. **AES-256-GCM**: Authenticated encryption
|
|
189
|
+
- Provides confidentiality and integrity
|
|
190
|
+
- 12-byte IV, 16-byte auth tag
|
|
191
|
+
|
|
192
|
+
4. **Memory Safety**: Key buffers are zeroed after use
|
|
193
|
+
- Reduces exposure window for sensitive key material
|
|
194
|
+
|
|
195
|
+
## Error Handling
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import {
|
|
199
|
+
AMKUnavailableError,
|
|
200
|
+
TenantKeyMissingError,
|
|
201
|
+
EncryptionError,
|
|
202
|
+
DecryptionError,
|
|
203
|
+
} from '@happyvertical/secrets';
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
await store.encrypt('tenant-123', 'secret', 'value');
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof AMKUnavailableError) {
|
|
209
|
+
// AMK not configured or inaccessible
|
|
210
|
+
} else if (error instanceof TenantKeyMissingError) {
|
|
211
|
+
// Tenant doesn't have a key - create one first
|
|
212
|
+
} else if (error instanceof EncryptionError) {
|
|
213
|
+
// Encryption failed
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## License
|
|
219
|
+
|
|
220
|
+
MIT
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ApplicationMasterKey, DatabaseSecretStoreOptions, DecryptedSecret, EncryptedEnvelope, EncryptOptions, SecretStore, SecretStoreEventListener, TenantDataEncryptionKey, Unsubscribe } from '../shared/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Database-backed secret store
|
|
4
|
+
*
|
|
5
|
+
* Uses envelope encryption with AES-256-GCM:
|
|
6
|
+
* - Application Master Key (AMK) from environment variable
|
|
7
|
+
* - Per-tenant Data Encryption Keys (TDEKs) wrapped by AMK
|
|
8
|
+
* - Secrets encrypted by unwrapped TDEKs
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const store = new DatabaseSecretStore({
|
|
13
|
+
* type: 'database',
|
|
14
|
+
* db,
|
|
15
|
+
* amk: {
|
|
16
|
+
* provider: 'env',
|
|
17
|
+
* keyEnvVar: 'SECRET_MASTER_KEY',
|
|
18
|
+
* keyId: 'amk-v1'
|
|
19
|
+
* }
|
|
20
|
+
* });
|
|
21
|
+
*
|
|
22
|
+
* await store.initialize();
|
|
23
|
+
*
|
|
24
|
+
* // Encrypt a secret for a tenant
|
|
25
|
+
* const envelope = await store.encrypt('tenant-123', 'api-key', 'sk_live_xxx');
|
|
26
|
+
*
|
|
27
|
+
* // Decrypt
|
|
28
|
+
* const { value } = await store.decrypt('tenant-123', envelope);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare class DatabaseSecretStore implements SecretStore {
|
|
32
|
+
private db;
|
|
33
|
+
private keysTable;
|
|
34
|
+
private amkConfig;
|
|
35
|
+
private cachedAmk;
|
|
36
|
+
private initialized;
|
|
37
|
+
private listeners;
|
|
38
|
+
constructor(options: DatabaseSecretStoreOptions);
|
|
39
|
+
/**
|
|
40
|
+
* Initialize the store (create schema if needed)
|
|
41
|
+
*/
|
|
42
|
+
initialize(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Get the Application Master Key from environment
|
|
45
|
+
*/
|
|
46
|
+
private getAMK;
|
|
47
|
+
/**
|
|
48
|
+
* Ensure the store is initialized
|
|
49
|
+
*/
|
|
50
|
+
private ensureInitialized;
|
|
51
|
+
/**
|
|
52
|
+
* Emit an event to all listeners
|
|
53
|
+
*/
|
|
54
|
+
private emitEvent;
|
|
55
|
+
/**
|
|
56
|
+
* Subscribe to store events
|
|
57
|
+
*/
|
|
58
|
+
subscribe(listener: SecretStoreEventListener): Unsubscribe;
|
|
59
|
+
encrypt(tenantId: string, secretName: string, plaintext: string, options?: EncryptOptions): Promise<EncryptedEnvelope>;
|
|
60
|
+
decrypt(tenantId: string, envelope: EncryptedEnvelope): Promise<DecryptedSecret>;
|
|
61
|
+
getTenantKey(tenantId: string): Promise<TenantDataEncryptionKey | null>;
|
|
62
|
+
createTenantKey(tenantId: string): Promise<TenantDataEncryptionKey>;
|
|
63
|
+
rotateTenantKey(tenantId: string): Promise<TenantDataEncryptionKey>;
|
|
64
|
+
retireTenantKey(tenantId: string, keyId: string): Promise<void>;
|
|
65
|
+
getActiveAMK(): Promise<ApplicationMasterKey>;
|
|
66
|
+
rewrapTenantKey(_tenantId: string, _newAmkKeyId: string): Promise<TenantDataEncryptionKey>;
|
|
67
|
+
listKeyVersions(tenantId: string): Promise<TenantDataEncryptionKey[]>;
|
|
68
|
+
/**
|
|
69
|
+
* Parse a database row into a TenantDataEncryptionKey
|
|
70
|
+
*/
|
|
71
|
+
private parseKeyRow;
|
|
72
|
+
}
|
|
73
|
+
export default DatabaseSecretStore;
|
|
74
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/adapters/database.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,oBAAoB,EACpB,0BAA0B,EAC1B,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,WAAW,EAEX,wBAAwB,EACxB,uBAAuB,EACvB,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAO5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAoC;IACrD,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAAkC;gBAEvC,OAAO,EAAE,0BAA0B;IAe/C;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA+BjC;;OAEG;IACH,OAAO,CAAC,MAAM;IAuBd;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;OAEG;YACW,SAAS;IAMvB;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,wBAAwB,GAAG,WAAW;IAUpD,OAAO,CACX,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,iBAAiB,CAAC;IA8EvB,OAAO,CACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAAC,eAAe,CAAC;IAiErB,YAAY,CAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC;IAapC,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IA6CnE,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,CAAC;IA6EnE,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB/D,YAAY,IAAI,OAAO,CAAC,oBAAoB,CAAC;IAc7C,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,uBAAuB,CAAC;IAY7B,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC;IAW3E;;OAEG;IACH,OAAO,CAAC,WAAW;CAiBpB;AAED,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-context.d.ts","sourceRoot":"","sources":["../../src/cli/claude-context.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, copyFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const Dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const pkgRoot = join(Dirname, "../..");
|
|
7
|
+
const targetDir = join(process.cwd(), ".claude");
|
|
8
|
+
if (!existsSync(targetDir)) {
|
|
9
|
+
mkdirSync(targetDir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
const pkgName = "secrets";
|
|
12
|
+
const agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
|
|
13
|
+
const metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
|
|
14
|
+
if (existsSync(agentMdSrc)) {
|
|
15
|
+
copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
|
|
16
|
+
}
|
|
17
|
+
if (existsSync(metaSrc)) {
|
|
18
|
+
copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
|
|
19
|
+
}
|
|
20
|
+
console.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);
|
|
21
|
+
//# sourceMappingURL=claude-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-context.js","sources":["../../src/cli/claude-context.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI script to install agent context for @happyvertical/secrets\n * Run the published context installer binary for this package.\n */\nimport { copyFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst Dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgRoot = join(Dirname, '../..');\nconst targetDir = join(process.cwd(), '.claude');\n\nif (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n}\n\nconst pkgName = 'secrets';\nconst agentMdSrc = existsSync(join(pkgRoot, 'AGENT.md'))\n ? join(pkgRoot, 'AGENT.md')\n : join(pkgRoot, 'CLAUDE.md');\nconst metaSrc = existsSync(join(pkgRoot, 'metadata.json'))\n ? join(pkgRoot, 'metadata.json')\n : join(pkgRoot, '.claude-meta.json');\n\nif (existsSync(agentMdSrc)) {\n copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));\n}\n\nif (existsSync(metaSrc)) {\n copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));\n}\n\nconsole.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);\n"],"names":[],"mappings":";;;;AASA,MAAM,UAAU,QAAQ,cAAc,YAAY,GAAG,CAAC;AACtD,MAAM,UAAU,KAAK,SAAS,OAAO;AACrC,MAAM,YAAY,KAAK,QAAQ,IAAA,GAAO,SAAS;AAE/C,IAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,YAAU,WAAW,EAAE,WAAW,KAAA,CAAM;AAC1C;AAEA,MAAM,UAAU;AAChB,MAAM,aAAa,WAAW,KAAK,SAAS,UAAU,CAAC,IACnD,KAAK,SAAS,UAAU,IACxB,KAAK,SAAS,WAAW;AAC7B,MAAM,UAAU,WAAW,KAAK,SAAS,eAAe,CAAC,IACrD,KAAK,SAAS,eAAe,IAC7B,KAAK,SAAS,mBAAmB;AAErC,IAAI,WAAW,UAAU,GAAG;AAC1B,eAAa,YAAY,KAAK,WAAW,QAAQ,OAAO,KAAK,CAAC;AAChE;AAEA,IAAI,WAAW,OAAO,GAAG;AACvB,eAAa,SAAS,KAAK,WAAW,QAAQ,OAAO,YAAY,CAAC;AACpE;AAEA,QAAQ,IAAI,8BAA8B,OAAO,sBAAsB;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @happyvertical/secrets
|
|
3
|
+
*
|
|
4
|
+
* Envelope encryption for per-tenant secret management with pluggable backends.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { getSecretStore } from '@happyvertical/secrets';
|
|
9
|
+
* import { getDatabase } from '@happyvertical/sql';
|
|
10
|
+
*
|
|
11
|
+
* // Get a database connection
|
|
12
|
+
* const db = await getDatabase({ type: 'sqlite', url: ':memory:' });
|
|
13
|
+
*
|
|
14
|
+
* // Create a secret store
|
|
15
|
+
* const store = await getSecretStore({
|
|
16
|
+
* type: 'database',
|
|
17
|
+
* db,
|
|
18
|
+
* amk: {
|
|
19
|
+
* provider: 'env',
|
|
20
|
+
* keyEnvVar: 'SECRET_MASTER_KEY', // 64 hex chars (32 bytes)
|
|
21
|
+
* keyId: 'amk-v1'
|
|
22
|
+
* }
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Encrypt a secret for a tenant
|
|
26
|
+
* const envelope = await store.encrypt('tenant-123', 'api-key', 'sk_live_xxx');
|
|
27
|
+
*
|
|
28
|
+
* // Decrypt the secret
|
|
29
|
+
* const { value } = await store.decrypt('tenant-123', envelope);
|
|
30
|
+
* console.log(value); // 'sk_live_xxx'
|
|
31
|
+
*
|
|
32
|
+
* // Rotate tenant's encryption key
|
|
33
|
+
* await store.rotateTenantKey('tenant-123');
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @packageDocumentation
|
|
37
|
+
*/
|
|
38
|
+
export { DatabaseSecretStore } from './adapters/database.js';
|
|
39
|
+
export { EnvelopeEncryption } from './shared/envelope.js';
|
|
40
|
+
export { AMKUnavailableError, DecryptionError, EncryptionError, InvalidKeyFormatError, KeyNotFoundError, KeyRotationError, SecretError, StoreNotInitializedError, TenantKeyMissingError, } from './shared/errors.js';
|
|
41
|
+
export { getSecretStore, isAWSKMSOptions, isAzureKeyVaultOptions, isDatabaseOptions, isVaultOptions, } from './shared/factory.js';
|
|
42
|
+
export type { AMKConfig, ApplicationMasterKey, AWSKMSSecretStoreOptions, AzureKeyVaultSecretStoreOptions, DatabaseSecretStoreOptions, DecryptedSecret, EncryptedEnvelope, EncryptOptions, GetSecretStoreOptions, SecretAdapterType, SecretStore, SecretStoreEvent, SecretStoreEventListener, SecretStoreEventType, TenantDataEncryptionKey, Unsubscribe, VaultSecretStoreOptions, } from './shared/types.js';
|
|
43
|
+
/** @internal */
|
|
44
|
+
export declare const PACKAGE_VERSION_INITIALIZED = true;
|
|
45
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EACL,cAAc,EACd,eAAe,EACf,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,GACf,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAEV,SAAS,EACT,oBAAoB,EACpB,wBAAwB,EACxB,+BAA+B,EAC/B,0BAA0B,EAC1B,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACX,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,uBAAuB,EACvB,WAAW,EACX,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAE3B,gBAAgB;AAChB,eAAO,MAAM,2BAA2B,OAAO,CAAC"}
|