@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 { SecretService } from '@pikku/core/services';
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":"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"}
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.14",
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.74"
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, { 'batch-a': { v: 1 }, 'batch-b': 'two' })
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, { present: 'here' })
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 { SecretService } from '@pikku/core/services'
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 envelopeDecrypt<T>(kek, data.ciphertext!, data.wrapped_dek!)
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] = await envelopeDecrypt(kek, data.ciphertext, data.wrapped_dek!)
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> {