• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

safe-global / safe-client-gateway / 22680297835

04 Mar 2026 05:06PM UTC coverage: 89.301%. First build
22680297835

Pull #2963

github

vseehausen
feat(encryption): add database migration and backfill service (WA-1647)

Add migration to widen address/name columns to TEXT, add address_hash
columns with partial unique indexes, and drop old deterministic unique
constraints. Backfill service encrypts existing plaintext rows in
batches via subscriber/transformer hooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pull Request #2963: feat(encryption): spaces data encryption

3029 of 3838 branches covered (78.92%)

Branch coverage included in aggregate %.

173 of 192 new or added lines in 19 files covered. (90.1%)

14674 of 15986 relevant lines covered (91.79%)

529.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.41
/src/datasources/encryption/encryption.module.ts
1
// SPDX-License-Identifier: FSL-1.1-MIT
2
import { DynamicModule, Global, Module } from '@nestjs/common';
96✔
3
import { DecryptCommand, KMSClient } from '@aws-sdk/client-kms';
96✔
4
import { IConfigurationService } from '@/config/configuration.service.interface';
96✔
5
import { IFieldEncryptionService } from '@/datasources/encryption/encryption.service.interface';
96✔
6
import { FieldEncryptionService } from '@/datasources/encryption/field-encryption.service';
96✔
7
import { EncryptionLocator } from '@/datasources/encryption/encryption-locator';
96✔
8
import type { ILoggingService } from '@/logging/logging.interface';
9
import { LoggingService } from '@/logging/logging.interface';
96✔
10

11
export enum EncryptionProvider {
96✔
12
  AWS = 'aws',
96✔
13
  LOCAL = 'local',
96✔
14
}
15

16
@Global()
17
@Module({})
18
export class EncryptionModule {
96✔
19
  static register(): DynamicModule {
20
    return {
1,570✔
21
      module: EncryptionModule,
22
      providers: [
23
        {
24
          provide: IFieldEncryptionService,
25
          useFactory: async (
26
            configService: IConfigurationService,
27
            loggingService: ILoggingService,
28
          ): Promise<IFieldEncryptionService> => {
29
            const provider = configService.getOrThrow<string>(
1,572✔
30
              'encryption.provider',
31
            );
32

33
            let service: IFieldEncryptionService;
34

35
            switch (provider as EncryptionProvider) {
1,572!
36
              case EncryptionProvider.AWS: {
NEW
37
                const kmsClient = new KMSClient({});
×
38

NEW
39
                const dekEncrypted = configService.getOrThrow<string>(
×
40
                  'encryption.dekV1Encrypted',
41
                );
NEW
42
                const hmacKeyEncrypted = configService.getOrThrow<string>(
×
43
                  'encryption.hmacKeyEncrypted',
44
                );
45

NEW
46
                const [dekResult, hmacResult] = await Promise.all([
×
47
                  kmsClient.send(
48
                    new DecryptCommand({
49
                      CiphertextBlob: Buffer.from(dekEncrypted, 'base64'),
50
                    }),
51
                  ),
52
                  kmsClient.send(
53
                    new DecryptCommand({
54
                      CiphertextBlob: Buffer.from(hmacKeyEncrypted, 'base64'),
55
                    }),
56
                  ),
57
                ]);
58

NEW
59
                if (!dekResult.Plaintext || !hmacResult.Plaintext) {
×
NEW
60
                  throw new Error(
×
61
                    'KMS Decrypt returned empty plaintext for DEK or HMAC key',
62
                  );
63
                }
64

NEW
65
                service = new FieldEncryptionService(
×
66
                  Buffer.from(dekResult.Plaintext),
67
                  Buffer.from(hmacResult.Plaintext),
68
                );
69

NEW
70
                loggingService.info('Encryption initialized with AWS KMS');
×
NEW
71
                break;
×
72
              }
73
              case EncryptionProvider.LOCAL: {
74
                const localKey = Buffer.from(
1,572✔
75
                  configService.getOrThrow<string>('encryption.localKey'),
76
                  'hex',
77
                );
78
                const hmacSecret = Buffer.from(
1,572✔
79
                  configService.getOrThrow<string>('encryption.hmacSecret'),
80
                  'hex',
81
                );
82

83
                service = new FieldEncryptionService(localKey, hmacSecret);
1,572✔
84

85
                loggingService.info(
1,572✔
86
                  'Encryption initialized with local provider',
87
                );
88
                break;
1,572✔
89
              }
90
              default:
NEW
91
                throw new Error(
×
92
                  `Unknown encryption provider: ${provider}. Use 'aws' or 'local'.`,
93
                );
94
            }
95

96
            EncryptionLocator.setService(service);
1,572✔
97
            return service;
1,572✔
98
          },
99
          inject: [IConfigurationService, LoggingService],
100
        },
101
      ],
102
      exports: [IFieldEncryptionService],
103
    };
104
  }
105
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc