@pikku/redis 0.12.14 → 0.12.15
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
+
## 0.12.15
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- 78b29f0: `SecretService` now returns a `SecretValue<T>` rather than the bare value, so a
|
|
6
|
+
vault secret cannot reach a sink by accident.
|
|
7
|
+
|
|
8
|
+
`SecretValue` is nominally typed, which means it is not assignable to `string`
|
|
9
|
+
(or to any other concretely-typed field). Every sink with a real type — a
|
|
10
|
+
database column, an email body, a session payload — rejects it with no lint
|
|
11
|
+
rule involved. The sinks typed `any`, `unknown`, or a free generic — the logger,
|
|
12
|
+
queue payloads, webhook and email inputs, and a function's own output — are
|
|
13
|
+
guarded with `Safe<T>`, which collapses a `SecretValue` found anywhere inside
|
|
14
|
+
`T`, however deeply nested, to `never`.
|
|
15
|
+
|
|
16
|
+
Unwrap deliberately at the point the secret reaches the wire:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
const secret = await secrets.getSecret('BETTER_AUTH_SECRET')
|
|
20
|
+
betterAuth({ secret: secret.reveal() })
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Two behaviours cover what types cannot see. Structured serialization redacts —
|
|
24
|
+
`JSON.stringify` and node's inspect both yield `[secret]`, so an audit or log
|
|
25
|
+
write stays honest without crashing the request. String coercion throws
|
|
26
|
+
`SecretCoercionError`, because a template literal is always a leak.
|
|
27
|
+
|
|
28
|
+
`AuditLog.write` is guarded the same way as the logger, since an audit event
|
|
29
|
+
carries `input` and `metadata` as `unknown` and nominality alone cannot stop a
|
|
30
|
+
secret landing in one.
|
|
31
|
+
|
|
32
|
+
`.reveal()` is the deliberate escape hatch, and what it hands back is an
|
|
33
|
+
ordinary string as far as every sink signature is concerned. **PKU953** closes
|
|
34
|
+
that gap: under `pikku all --security` the inspector reports a revealed secret
|
|
35
|
+
that flows into a logger, an audit, a queue, an email or a webhook — `console` included.
|
|
36
|
+
|
|
37
|
+
This also fixed a real one: `remote-addon-auth.ts` called `String(token)` on an
|
|
38
|
+
`unknown` and wrote the result straight into an `Authorization` header.
|
|
39
|
+
|
|
40
|
+
- Updated dependencies [62ea4cc]
|
|
41
|
+
- Updated dependencies [9dddff8]
|
|
42
|
+
- Updated dependencies [78b29f0]
|
|
43
|
+
- @pikku/core@0.12.76
|
|
44
|
+
|
|
1
45
|
## 0.12.14
|
|
2
46
|
|
|
3
47
|
### Patch Changes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type SecretValue } from '@pikku/core';
|
|
2
|
+
import type { SecretService, SecretValues } from '@pikku/core/services';
|
|
2
3
|
import { Redis, type RedisOptions } from 'ioredis';
|
|
3
4
|
export interface RedisSecretServiceConfig {
|
|
4
5
|
key: string;
|
|
@@ -20,11 +21,11 @@ export declare class RedisSecretService implements SecretService {
|
|
|
20
21
|
private saltKey;
|
|
21
22
|
private getKEKSalt;
|
|
22
23
|
private getKEK;
|
|
23
|
-
getSecret<T = string>(key: string): Promise<T
|
|
24
|
+
getSecret<T = string>(key: string): Promise<SecretValue<T>>;
|
|
24
25
|
hasSecret(key: string): Promise<boolean>;
|
|
25
26
|
setSecret(key: string, value: unknown): Promise<void>;
|
|
26
27
|
deleteSecret(key: string): Promise<void>;
|
|
27
|
-
getSecrets<T extends Record<string, unknown> = Record<string, unknown>>(keys: (keyof T & string)[]): Promise<T
|
|
28
|
+
getSecrets<T extends Record<string, unknown> = Record<string, unknown>>(keys: (keyof T & string)[]): Promise<Partial<SecretValues<T>>>;
|
|
28
29
|
rotateKEK(): Promise<number>;
|
|
29
30
|
close(): Promise<void>;
|
|
30
31
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createSecretValue, isSecretValue } from '@pikku/core';
|
|
1
2
|
import { Redis } from 'ioredis';
|
|
2
3
|
import { deriveKEK, generateKEKSalt, envelopeEncrypt, envelopeDecrypt, envelopeRewrap, } from '@pikku/core/crypto-utils';
|
|
3
4
|
export class RedisSecretService {
|
|
@@ -68,14 +69,14 @@ export class RedisSecretService {
|
|
|
68
69
|
if (!data.ciphertext)
|
|
69
70
|
throw new Error('Requested secret not found');
|
|
70
71
|
const kek = await this.getKEK(Number(data.key_version));
|
|
71
|
-
return envelopeDecrypt(kek, data.ciphertext, data.wrapped_dek);
|
|
72
|
+
return createSecretValue(await envelopeDecrypt(kek, data.ciphertext, data.wrapped_dek));
|
|
72
73
|
}
|
|
73
74
|
async hasSecret(key) {
|
|
74
75
|
const exists = await this.redis.exists(this.secretKey(key));
|
|
75
76
|
return exists === 1;
|
|
76
77
|
}
|
|
77
78
|
async setSecret(key, value) {
|
|
78
|
-
const { ciphertext, wrappedDEK } = await envelopeEncrypt(await this.getKEK(this.keyVersion), value);
|
|
79
|
+
const { ciphertext, wrappedDEK } = await envelopeEncrypt(await this.getKEK(this.keyVersion), isSecretValue(value) ? value.reveal() : value);
|
|
79
80
|
await this.redis.hset(this.secretKey(key), {
|
|
80
81
|
ciphertext,
|
|
81
82
|
wrapped_dek: wrappedDEK,
|
|
@@ -97,7 +98,7 @@ export class RedisSecretService {
|
|
|
97
98
|
const keyVersion = Number(data.key_version);
|
|
98
99
|
try {
|
|
99
100
|
const kek = await this.getKEK(keyVersion);
|
|
100
|
-
out[key] = await envelopeDecrypt(kek, data.ciphertext, data.wrapped_dek);
|
|
101
|
+
out[key] = createSecretValue(await envelopeDecrypt(kek, data.ciphertext, data.wrapped_dek));
|
|
101
102
|
}
|
|
102
103
|
catch (cause) {
|
|
103
104
|
throw new Error(`Failed to decrypt secret "${key}" (key_version ${keyVersion}): ` +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-secret-service.js","sourceRoot":"","sources":["../../src/redis-secret-service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redis-secret-service.js","sourceRoot":"","sources":["../../src/redis-secret-service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/redis",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.15",
|
|
4
4
|
"description": "Redis services for Pikku — workflow state, agent runs, secrets, sessions and channel stores",
|
|
5
5
|
"author": "yasser.fadl@gmail.com",
|
|
6
6
|
"license": "MIT",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"prepublishOnly": "yarn build"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@pikku/core": "^0.12.
|
|
21
|
+
"@pikku/core": "^0.12.76"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"ioredis": "^5.10.0"
|
|
@@ -41,8 +41,8 @@ describe('RedisSecretService', () => {
|
|
|
41
41
|
const r1 = await s1.getSecret<{ from: string }>('shared-name')
|
|
42
42
|
const r2 = await s2.getSecret<{ from: string }>('shared-name')
|
|
43
43
|
|
|
44
|
-
assert.deepEqual(r1, { from: 'app1' })
|
|
45
|
-
assert.deepEqual(r2, { from: 'app2' })
|
|
44
|
+
assert.deepEqual(r1.reveal(), { from: 'app1' })
|
|
45
|
+
assert.deepEqual(r2.reveal(), { from: 'app2' })
|
|
46
46
|
prefixRedis.disconnect()
|
|
47
47
|
})
|
|
48
48
|
|
|
@@ -105,7 +105,8 @@ describe('RedisSecretService', () => {
|
|
|
105
105
|
'batch-b': string
|
|
106
106
|
}>(['batch-a', 'batch-b'])
|
|
107
107
|
|
|
108
|
-
assert.deepEqual(out
|
|
108
|
+
assert.deepEqual(out['batch-a']!.reveal(), { v: 1 })
|
|
109
|
+
assert.equal(out['batch-b']!.reveal(), 'two')
|
|
109
110
|
})
|
|
110
111
|
|
|
111
112
|
test('getSecrets omits keys that were never stored', async () => {
|
|
@@ -114,7 +115,8 @@ describe('RedisSecretService', () => {
|
|
|
114
115
|
|
|
115
116
|
const out = await service.getSecrets(['present', 'never-stored'])
|
|
116
117
|
|
|
117
|
-
assert.deepEqual(out,
|
|
118
|
+
assert.deepEqual(Object.keys(out), ['present'])
|
|
119
|
+
assert.equal(out.present!.reveal(), 'here')
|
|
118
120
|
})
|
|
119
121
|
})
|
|
120
122
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { createSecretValue, isSecretValue, type SecretValue } from '@pikku/core'
|
|
2
|
+
import type { SecretService, SecretValues } from '@pikku/core/services'
|
|
2
3
|
import { Redis, type RedisOptions } from 'ioredis'
|
|
3
4
|
import {
|
|
4
5
|
deriveKEK,
|
|
@@ -92,12 +93,14 @@ export class RedisSecretService implements SecretService {
|
|
|
92
93
|
return kek
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
async getSecret<T = string>(key: string): Promise<T
|
|
96
|
+
async getSecret<T = string>(key: string): Promise<SecretValue<T>> {
|
|
96
97
|
const data = await this.redis.hgetall(this.secretKey(key))
|
|
97
98
|
if (!data.ciphertext) throw new Error('Requested secret not found')
|
|
98
99
|
|
|
99
100
|
const kek = await this.getKEK(Number(data.key_version))
|
|
100
|
-
return
|
|
101
|
+
return createSecretValue(
|
|
102
|
+
await envelopeDecrypt<T>(kek, data.ciphertext!, data.wrapped_dek!)
|
|
103
|
+
)
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
async hasSecret(key: string): Promise<boolean> {
|
|
@@ -106,9 +109,11 @@ export class RedisSecretService implements SecretService {
|
|
|
106
109
|
}
|
|
107
110
|
|
|
108
111
|
async setSecret(key: string, value: unknown): Promise<void> {
|
|
112
|
+
// Encrypting the wrapper would store its redaction, not the secret —
|
|
113
|
+
// writing a secret back to the vault is exactly what this method is for.
|
|
109
114
|
const { ciphertext, wrappedDEK } = await envelopeEncrypt(
|
|
110
115
|
await this.getKEK(this.keyVersion),
|
|
111
|
-
value
|
|
116
|
+
isSecretValue(value) ? value.reveal() : value
|
|
112
117
|
)
|
|
113
118
|
|
|
114
119
|
await this.redis.hset(this.secretKey(key), {
|
|
@@ -124,7 +129,7 @@ export class RedisSecretService implements SecretService {
|
|
|
124
129
|
|
|
125
130
|
async getSecrets<T extends Record<string, unknown> = Record<string, unknown>>(
|
|
126
131
|
keys: (keyof T & string)[]
|
|
127
|
-
): Promise<T
|
|
132
|
+
): Promise<Partial<SecretValues<T>>> {
|
|
128
133
|
const rows = await Promise.all(
|
|
129
134
|
keys.map(async (key) => ({
|
|
130
135
|
key,
|
|
@@ -139,7 +144,9 @@ export class RedisSecretService implements SecretService {
|
|
|
139
144
|
const keyVersion = Number(data.key_version)
|
|
140
145
|
try {
|
|
141
146
|
const kek = await this.getKEK(keyVersion)
|
|
142
|
-
out[key] =
|
|
147
|
+
out[key] = createSecretValue(
|
|
148
|
+
await envelopeDecrypt(kek, data.ciphertext, data.wrapped_dek!)
|
|
149
|
+
)
|
|
143
150
|
} catch (cause) {
|
|
144
151
|
throw new Error(
|
|
145
152
|
`Failed to decrypt secret "${key}" (key_version ${keyVersion}): ` +
|
|
@@ -148,7 +155,7 @@ export class RedisSecretService implements SecretService {
|
|
|
148
155
|
)
|
|
149
156
|
}
|
|
150
157
|
}
|
|
151
|
-
return out as T
|
|
158
|
+
return out as Partial<SecretValues<T>>
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
async rotateKEK(): Promise<number> {
|