-
-
Notifications
You must be signed in to change notification settings - Fork 90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add @encrypted
enhancer
#1922
Conversation
📝 WalkthroughWalkthroughThis pull request introduces a new Changes
Assessment against linked issues
Possibly related PRs
Tip CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @genu , thanks a lot for contributing this feature! And I appreciate that you incorporated our latest discussions into it 😄.
Overall the changes look great to me. I've left a few comments. Please take a look. Thanks!
@encrypted
enhancer@encrypted
enhancer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (6)
packages/runtime/src/enhancements/node/encrypted.ts (2)
57-80
: Consider handling encryption errors more gracefully.
If an error occurs in the “await crypto.subtle.encrypt(...)” call (e.g., invalid key length), the caller receives a raw error. Depending on your architecture, you might consider catching and throwing a more descriptive error.
107-107
: Use optional chaining for readability.
Static analysis suggests changing “if (args && args.data && actionsOfInterest.includes(action))” to optional chaining:- if (args && args.data && actionsOfInterest.includes(action)) { + if (args?.data && actionsOfInterest.includes(action)) {🧰 Tools
🪛 Biome (1.9.4)
[error] 107-107: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/runtime/src/enhancements/edge/encrypted.ts (2)
17-32
: Identical logic to node environment is acceptable.
If the edge environment is truly the same, you might symlink as suggested before. Otherwise, duplicating logic is fine if separate platform-specific changes are expected.
107-107
: Consider optional chaining for args.
Similar to the node implementation, adopting optional chaining is a minor readability improvement.- if (args && args.data && actionsOfInterest.includes(action)) { + if (args?.data && actionsOfInterest.includes(action)) {🧰 Tools
🪛 Biome (1.9.4)
[error] 107-107: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/runtime/src/types.ts (1)
181-181
: Brief note: immutable encryption key.
When using a simple string-based key, confirm that future expansions won’t require key rotation logic or length checks.packages/schema/src/res/stdlib.zmodel (1)
556-562
: Allow configuring encryption secrets.Currently, the new @Encrypted() attribute does not provide a mechanism for specifying an encryption secret (or mentioning how it is fetched). Storing secrets directly in code or schema is generally discouraged. Consider adding a parameter (e.g., secret: String?) to allow retrieving the key from an environment variable using env("KEY"), or any other configuration system, to enable safer key management.
Would you like me to propose a code snippet that extends @Encrypted() to accept a secret parameter?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
packages/runtime/src/enhancements/edge/encrypted.ts
(1 hunks)packages/runtime/src/enhancements/node/create-enhancement.ts
(3 hunks)packages/runtime/src/enhancements/node/encrypted.ts
(1 hunks)packages/runtime/src/types.ts
(4 hunks)packages/schema/src/res/stdlib.zmodel
(1 hunks)tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts
(1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.21.2)
tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts
26-26: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🪛 Biome (1.9.4)
packages/runtime/src/enhancements/edge/encrypted.ts
[error] 107-107: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
packages/runtime/src/enhancements/node/encrypted.ts
[error] 107-107: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (15)
packages/runtime/src/enhancements/node/encrypted.ts (4)
17-32
: Looks good: Properly wires encryption enhancement into the Prisma proxy.
No issues found in this block.
53-55
: Explicit check for custom encryption.
This type guard cleanly distinguishes between custom and simple encryption. Nicely done.
81-102
: Consider adding a fallback strategy if decryption fails.
Should decryption fail (e.g., corrupted or invalid cipher), returning the original cipher text might simplify adoption, matching one of your colleague’s earlier comments.
145-145
: Add a nullish check to data before encrypting.
If “data” is null or undefined, encryption calls will fail. A quick check prevents runtime errors, echoing a past suggestion.
packages/runtime/src/enhancements/edge/encrypted.ts (2)
53-55
: Good check for custom vs. simple encryption.
This type guard remains consistent with the node version, ensuring a parallel architecture.
81-102
: Offer a fallback for decryption step.
When an invalid cipher string is encountered, consider reusing logic from the node version about fallback to the original string.
tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts (2)
47-50
: Great test verifying the encryption boundary.
Verifies that the stored value is encrypted for unauthorized read attempts yet decrypted for legitimate lookups.
98-101
: Custom encryption logic test is well-structured.
This scenario effectively demonstrates custom encryption features. Nicely done.
packages/runtime/src/types.ts (3)
138-141
: Encryption property inclusion is coherent.
The optional encryption field has a clear definition, making it straightforward to integrate.
154-154
: Including 'encrypted' in EnhancementKind is consistent.
This ensures the new enhancement is recognized when building enhancements for the Prisma client.
176-180
: Validate if a Uint8Array-based key is more suitable.
A prior comment suggested using a typed array instead of string. If you need stronger key validation, consider adopting that approach.
packages/runtime/src/enhancements/node/create-enhancement.ts (4)
17-17
: Import for withEncrypted is correct.
Cleanly pulls in the new encryption functionality.
24-24
: Adding 'encrypted' to ALL_ENHANCEMENTS is correct.
This ensures the new enhancement is part of the default pipeline.
104-104
: Efficient detection of @Encrypted usage.
Simple check for fields with the “@Encrypted” attribute. Nothing to add.
132-139
: Mandatory encryption options for @Encrypted.
Throwing an error if missing is appropriate, preventing incomplete configurations.
tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
packages/runtime/src/enhancements/edge/encrypted.ts (1)
Line range hint
27-34
: Consider enhancing crypto security measuresSeveral security improvements could be made:
- Use a proper key derivation function (KDF) instead of truncating the key
- Add error handling for invalid keys
- Consider using PBKDF2 or similar for key derivation
Example implementation:
const getKey = async (secret: string): Promise<CryptoKey> => { // Use PBKDF2 for key derivation const salt = new Uint8Array(16); const keyMaterial = await crypto.subtle.importKey( 'raw', encoder.encode(secret), 'PBKDF2', false, ['deriveBits', 'deriveKey'] ); return crypto.subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); };
🧹 Nitpick comments (3)
packages/runtime/src/enhancements/edge/encrypted.ts (3)
Line range hint
15-25
: Enhance function documentationThe
@private
tag suggests internal use, but the documentation could be more comprehensive:
- Add
@param
descriptions- Add
@returns
description- Document encryption options and requirements
- Add usage examples
Line range hint
36-48
: Add validation for encryption optionsThe constructor should validate encryption options to fail fast if they're invalid.
constructor(prisma: DbClientContract, model: string, options: InternalEnhancementOptions) { super(prisma, model, options); if (!options.encryption) { throw new Error('Encryption options are required for @encrypted enhancement'); } if (!this.isCustomEncryption(options.encryption) && !options.encryption.encryptionKey) { throw new Error('Encryption key is required for simple encryption'); } this.queryUtils = new QueryUtils(prisma, options); }
Line range hint
127-146
: Consider performance optimization for batch operationsThe
doPostProcess
method processes each field individually, which could be inefficient for batch operations. Consider:
- Batch decryption for multiple fields
- Caching the CryptoKey to avoid repeated key derivation
Example optimization:
private cryptoKeyCache: Map<string, Promise<CryptoKey>> = new Map(); private getCachedKey(secret: string): Promise<CryptoKey> { let key = this.cryptoKeyCache.get(secret); if (!key) { key = getKey(secret); this.cryptoKeyCache.set(secret, key); } return key; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/runtime/src/enhancements/edge/encrypted.ts
(1 hunks)
🔇 Additional comments (2)
packages/runtime/src/enhancements/edge/encrypted.ts (2)
1-1
: Consider using a symlink instead of duplicating the file
Since this file is identical to ../node/encrypted.ts
, consider using a symlink to avoid code duplication and maintain consistency.
Line range hint 49-124
: Verify security implementation across the codebase
Let's verify the security implementation and usage patterns.
✅ Verification successful
Let me gather more information about the encryption implementation and tests.
Security implementation verified with some recommendations
The encryption implementation shows good security practices with:
- Proper use of AES-GCM with secure IV generation
- No encryption key logging found in the codebase
- Support for custom encryption providers
- Comprehensive test coverage for both simple and custom encryption
However, there are a few points that need attention:
- The test file contains a hardcoded encryption key
'c558Gq0YQK2QcqtkMF9BGXHCQn4dMF8w'
. While this is acceptable for tests, ensure this is not used in production. - The key derivation function
getKey()
uses a 32-byte slice of the input secret. Consider adding input validation to ensure the provided secret has sufficient entropy. - Consider adding a mechanism to rotate encryption keys for long-term data security.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check for potential security issues in the implementation
# Look for other uses of crypto APIs to ensure consistent implementation
echo "Checking for crypto API usage patterns..."
rg -A 2 "crypto\.subtle\." --type ts
# Check for potential key exposure in logs
echo "Checking for potential key logging..."
rg "console\.(log|debug|info)" -A 2 -B 2 --type ts | rg -i "key|encrypt"
# Look for test files to ensure proper security testing
echo "Checking for encryption-related tests..."
fd -e test.ts -e spec.ts | rg -i "encrypt"
Length of output: 4470
Script:
#!/bin/bash
# Check encryption implementation details and tests
# Look for encryption-related test content
echo "Checking encryption test implementation..."
cat tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts
# Check for key generation and management
echo "Checking key management implementation..."
rg -A 5 "getKey" --type ts
# Look for encryption-related type definitions
echo "Checking encryption types..."
ast-grep --pattern 'interface $_ {
$$$
encrypt
$$$
}'
Length of output: 6020
…encoder/decoder initialization into the EncryptedHandler class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts (1)
26-29
:⚠️ Potential issueMove encryption key to environment variable
Even in test files, encryption keys should not be hardcoded in the source code. Consider moving this to an environment variable or a test configuration file.
- encryption: { encryptionKey: 'c558Gq0YQK2QcqtkMF9BGXHCQn4dMF8w' }, + encryption: { encryptionKey: process.env.TEST_ENCRYPTION_KEY },🧰 Tools
🪛 Gitleaks (8.21.2)
28-28: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
packages/runtime/src/enhancements/node/create-enhancement.ts (1)
132-139
: 🛠️ Refactor suggestionValidate encryption options structure
The encryption options validation should be more thorough:
- Validate the structure of both SimpleEncryption and CustomEncryption
- Check key format and length for SimpleEncryption
- Verify custom encryption methods are properly implemented
packages/runtime/src/enhancements/node/encrypted.ts (2)
87-108
: 🛠️ Refactor suggestionImprove error handling in decrypt method.
As suggested in the previous review, consider gracefully handling decryption failures by returning the original ciphertext. This would enable gradual adoption of the
@encrypted
attribute.private async decrypt(field: FieldInfo, data: string): Promise<string> { if (this.isCustomEncryption(this.options.encryption!)) { return this.options.encryption.decrypt(this.model, field, data); } + try { const key = await this.getKey(this.options.encryption!.encryptionKey); // Convert base64 back to bytes const bytes = Uint8Array.from(atob(data), (c) => c.charCodeAt(0)); // First 12 bytes are IV, rest is encrypted data const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: bytes.slice(0, 12), }, key, bytes.slice(12) ); return this.decoder.decode(decrypted); + } catch (error) { + console.warn(`Failed to decrypt field ${field.name} in model ${this.model}:`, error); + return data; // Return original ciphertext on failure + } }
132-147
: 🛠️ Refactor suggestionAdd nullish checks and improve type safety.
As suggested in the previous review, add nullish checks for data. Also, consider improving type safety with type guards.
private async doPostProcess(entityData: any, model: string) { + if (!entityData) return; + const realModel = this.queryUtils.getDelegateConcreteModel(model, entityData); for (const field of getModelFields(entityData)) { const fieldInfo = await resolveField(this.options.modelMeta, realModel, field); - if (!fieldInfo) { + if (!fieldInfo || !entityData[field]) { continue; } const shouldDecrypt = fieldInfo.attributes?.find((attr) => attr.name === '@encrypted'); if (shouldDecrypt) { entityData[field] = await this.decrypt(fieldInfo, entityData[field]); } } }
🧹 Nitpick comments (9)
tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts (2)
16-53
: Enhance test coverage with edge casesThe simple encryption test should include additional scenarios:
- Validation of encryption key format/length
- Empty string encryption
- Special characters in the encrypted value
- Very long string encryption
🧰 Tools
🪛 Gitleaks (8.21.2)
28-28: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
68-79
: Improve custom encryption exampleThe current example using string concatenation might mislead developers about security requirements. Consider using a more realistic encryption example that demonstrates:
- Proper cryptographic methods
- Error handling for invalid inputs
- Secure key management practices
packages/runtime/src/types.ts (2)
137-141
: Enhance encryption options documentationThe encryption options documentation should include:
- Security requirements and recommendations
- Key format specifications
- Usage examples
- Best practices for key management
176-179
: Add error handling types for encryption operationsThe CustomEncryption type should include error handling specifications:
- Define expected error types for encryption/decryption failures
- Document error scenarios (invalid input, key issues, etc.)
packages/runtime/src/enhancements/node/encrypted.ts (5)
22-32
: Consider improving type safety.The function implementation looks good, but consider these type-safety improvements:
- Add explicit return type annotation
- Consider constraining
DbClient
to a more specific type thanobject
-export function withEncrypted<DbClient extends object = any>( +export function withEncrypted<DbClient extends DbClientContract>( prisma: DbClient, options: InternalEnhancementOptions -): DbClient { +): DbClient & { $encrypted: true } {
39-53
: Enhance encryption options validation.While the validation logic is thorough, consider these improvements:
- More descriptive error messages including expected formats
- Validation of encryption key format/encoding
- Consider using a validation helper function for better organization
constructor(prisma: DbClientContract, model: string, options: InternalEnhancementOptions) { super(prisma, model, options); this.queryUtils = new QueryUtils(prisma, options); - if (!options.encryption) throw new Error('Encryption options must be provided'); + if (!options.encryption) { + throw new Error('Encryption options must be provided for @encrypted enhancement'); + } if (this.isCustomEncryption(options.encryption!)) { if (!options.encryption.encrypt || !options.encryption.decrypt) - throw new Error('Custom encryption must provide encrypt and decrypt functions'); + throw new Error('Custom encryption requires both encrypt() and decrypt() functions'); } else { - if (!options.encryption.encryptionKey) throw new Error('Encryption key must be provided'); - if (options.encryption.encryptionKey.length !== 32) throw new Error('Encryption key must be 32 bytes'); + if (!options.encryption.encryptionKey) { + throw new Error('Simple encryption requires an encryption key'); + } + if (!(options.encryption.encryptionKey instanceof Uint8Array)) { + throw new Error('Encryption key must be a Uint8Array'); + } + if (options.encryption.encryptionKey.length !== 32) { + throw new Error('Encryption key must be exactly 32 bytes (256 bits)'); + } }
111-117
: Use optional chaining and improve type safety.As suggested by static analysis, use optional chaining for better safety.
protected async preprocessArgs(action: PrismaProxyActions, args: any) { const actionsOfInterest: PrismaProxyActions[] = ['create', 'createMany', 'update', 'updateMany', 'upsert']; - if (args && args.data && actionsOfInterest.includes(action)) { + if (args?.data && actionsOfInterest.includes(action)) { await this.preprocessWritePayload(this.model, action as PrismaWriteActionType, args); } return args; }🧰 Tools
🪛 Biome (1.9.4)
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
149-160
: Consider adding input validation for write payload processing.The
preprocessWritePayload
method should validate its inputs before processing.private async preprocessWritePayload(model: string, action: PrismaWriteActionType, args: any) { + if (!model || !action || !args) { + throw new Error('Invalid arguments for preprocessWritePayload'); + } + const visitor = new NestedWriteVisitor(this.options.modelMeta, { field: async (field, _action, data, context) => { const encAttr = field.attributes?.find((attr) => attr.name === '@encrypted'); if (encAttr && field.type === 'String') { + if (data === null || data === undefined) return; context.parent[field.name] = await this.encrypt(field, data); } }, }); await visitor.visit(model, action, args); }
1-161
: Consider additional security measures.While the current implementation is secure, consider these security improvements:
- Add key rotation support by including a version tag in the encrypted data
- Add integrity checks for custom encryption implementations
- Consider adding rate limiting for crypto operations
Would you like me to provide a detailed implementation for any of these security improvements?
🧰 Tools
🪛 Biome (1.9.4)
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/runtime/src/enhancements/node/create-enhancement.ts
(3 hunks)packages/runtime/src/enhancements/node/encrypted.ts
(1 hunks)packages/runtime/src/types.ts
(4 hunks)tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/runtime/src/enhancements/node/encrypted.ts
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🪛 Gitleaks (8.21.2)
tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts
28-28: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
🔇 Additional comments (1)
packages/runtime/src/enhancements/node/create-enhancement.ts (1)
125-140
: LGTM! Enhancement order is correct
The encrypted enhancement is correctly placed before the policy enhancement to maintain field length consistency, similar to the password enhancement.
…es in EncryptedHandler
…pty string values in EncryptedHandler
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (3)
packages/runtime/src/enhancements/node/encrypted.ts (3)
17-32
: Enhance documentation for better maintainability.While the function is well-implemented, the documentation could be more comprehensive. Consider adding:
@param
descriptions forprisma
andoptions
@returns
description- Example usage
- Why it's marked as
@private
/** * Gets an enhanced Prisma client that supports `@encrypted` attribute. + * + * @param prisma - The Prisma client instance to enhance + * @param options - Enhancement options including encryption configuration + * @returns Enhanced Prisma client with encryption support + * @example + * ```ts + * const enhanced = withEncrypted(prisma, { + * encryption: { + * encryptionKey: new Uint8Array(32) + * } + * }); + * ``` * * @private */
44-52
: Enhance encryption key validation and error messages.The validation could be more robust and provide more helpful error messages:
- Error messages should guide users on how to fix the issue
- For simple encryption, consider validating that the key isn't all zeros
- if (!options.encryption.encryptionKey) throw new Error('Encryption key must be provided'); - if (options.encryption.encryptionKey.length !== 32) throw new Error('Encryption key must be 32 bytes'); + if (!options.encryption.encryptionKey) { + throw new Error('Encryption key must be provided. Generate a secure 32-byte key using crypto.getRandomValues().'); + } + if (options.encryption.encryptionKey.length !== 32) { + throw new Error('Encryption key must be exactly 32 bytes. Current length: ' + + options.encryption.encryptionKey.length); + } + // Validate key isn't all zeros + if (options.encryption.encryptionKey.every(byte => byte === 0)) { + throw new Error('Encryption key must not be all zeros. Generate a secure key using crypto.getRandomValues().'); + }
111-117
: Use optional chaining as suggested by static analysis.The static analysis tool correctly identifies an opportunity to use optional chaining.
protected async preprocessArgs(action: PrismaProxyActions, args: any) { const actionsOfInterest: PrismaProxyActions[] = ['create', 'createMany', 'update', 'updateMany', 'upsert']; - if (args && args.data && actionsOfInterest.includes(action)) { + if (args?.data && actionsOfInterest.includes(action)) { await this.preprocessWritePayload(this.model, action as PrismaWriteActionType, args); } return args; }🧰 Tools
🪛 Biome (1.9.4)
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/runtime/src/enhancements/node/encrypted.ts
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/runtime/src/enhancements/node/encrypted.ts
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/runtime/src/enhancements/node/encrypted.ts (1)
87-108
:⚠️ Potential issueImprove error handling in decrypt method.
As discussed in previous reviews, consider handling decryption failures gracefully to allow gradual adoption of encryption.
private async decrypt(field: FieldInfo, data: string): Promise<string> { if (this.isCustomEncryption(this.options.encryption!)) { - return this.options.encryption.decrypt(this.model, field, data); + try { + return await this.options.encryption.decrypt(this.model, field, data); + } catch (error) { + console.warn(`Failed to decrypt field ${field.name} in model ${this.model}:`, error); + return data; + } } const key = await this.getKey(this.options.encryption!.encryptionKey); + try { // Convert base64 back to bytes const bytes = Uint8Array.from(atob(data), (c) => c.charCodeAt(0)); // First 12 bytes are IV, rest is encrypted data const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: bytes.slice(0, 12), }, key, bytes.slice(12) ); return this.decoder.decode(decrypted); + } catch (error) { + console.warn(`Failed to decrypt field ${field.name} in model ${this.model}:`, error); + return data; + } }
🧹 Nitpick comments (4)
packages/runtime/src/enhancements/node/encrypted.ts (4)
17-32
: Enhance API documentation for better maintainability.While the function is marked as @Private, consider adding more detailed documentation:
- Document the encryption options expected
- Add example usage
- Document return value and possible errors
/** * Gets an enhanced Prisma client that supports `@encrypted` attribute. * + * @param prisma - The Prisma client instance to enhance + * @param options - Enhancement options including encryption configuration + * @returns Enhanced Prisma client with encryption support + * @throws {Error} When encryption options are invalid * @private */
44-52
: Improve error messages and extract magic number.The validation logic could be improved by:
- Making error messages more descriptive
- Extracting the key length as a named constant
+const ENCRYPTION_KEY_LENGTH = 32; // AES-256 key length in bytes + if (!options.encryption) throw new Error('Encryption options must be provided'); if (this.isCustomEncryption(options.encryption!)) { if (!options.encryption.encrypt || !options.encryption.decrypt) - throw new Error('Custom encryption must provide encrypt and decrypt functions'); + throw new Error('Custom encryption requires both encrypt and decrypt functions to be implemented'); } else { - if (!options.encryption.encryptionKey) throw new Error('Encryption key must be provided'); - if (options.encryption.encryptionKey.length !== 32) throw new Error('Encryption key must be 32 bytes'); + if (!options.encryption.encryptionKey) throw new Error('Simple encryption requires an encryption key'); + if (options.encryption.encryptionKey.length !== ENCRYPTION_KEY_LENGTH) + throw new Error(`Encryption key must be ${ENCRYPTION_KEY_LENGTH} bytes (AES-256)`); }
111-117
: Use optional chaining for safer property access.The condition can be simplified using optional chaining.
-if (args && args.data && actionsOfInterest.includes(action)) { +if (args?.data && actionsOfInterest.includes(action)) {🧰 Tools
🪛 Biome (1.9.4)
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
1-167
: Security considerations for encryption implementation.The implementation follows cryptographic best practices but consider these security improvements:
- Key rotation: Add support for key rotation to handle compromised keys
- Key derivation: Consider using a key derivation function (KDF) instead of raw keys
- Logging: Ensure encryption errors are logged securely without exposing sensitive data
Would you like me to provide a detailed implementation for any of these security improvements?
🧰 Tools
🪛 Biome (1.9.4)
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/runtime/src/enhancements/node/encrypted.ts
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/runtime/src/enhancements/node/encrypted.ts
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (1)
packages/runtime/src/enhancements/node/encrypted.ts (1)
152-166
: Add defensive checks in preprocessWritePayload.
The field visitor could be more defensive against malformed data.
private async preprocessWritePayload(model: string, action: PrismaWriteActionType, args: any) {
+ if (!args?.data) {
+ return;
+ }
const visitor = new NestedWriteVisitor(this.options.modelMeta, {
field: async (field, _action, data, context) => {
// Don't encrypt null, undefined or empty string values
if (!data) return;
const encAttr = field.attributes?.find((attr) => attr.name === '@encrypted');
- if (encAttr && field.type === 'String') {
+ if (encAttr && field.type === 'String' && context?.parent) {
context.parent[field.name] = await this.encrypt(field, data);
}
},
});
await visitor.visit(model, action, args);
}
Let's verify the usage of context.parent:
✅ Verification successful
Let me gather more information about the NestedWriteVisitor implementation to understand the context structure better.
Let me check the NestedWriterVisitorCallback interface to understand the field callback structure better.
Let me try a different approach to find the interface definition.
Let me check the NestedWriteVisitorContext type to understand the context structure.
Based on the implementation of NestedWriteVisitor and the context structure, I can now provide the final response:
The suggested defensive checks are appropriate and necessary
The review comment's suggested changes are valid for the following reasons:
- The
args?.data
check is necessary as thepreprocessWritePayload
method receives raw arguments that might be undefined or missing the data property - The
context?.parent
check is important because:- The context object is passed to field visitors and contains the parent data object that will be modified
- The parent property is documented as "Parent data, can be used to replace fields" in NestedWriteVisitorContext
- The field visitor uses this parent object to update encrypted field values
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for NestedWriteVisitor usage to understand context structure
rg -A 10 "class NestedWriteVisitor"
Length of output: 988
Script:
#!/bin/bash
# Get the full implementation of NestedWriteVisitor
ast-grep --pattern 'class NestedWriteVisitor {
$$$
}'
Length of output: 24287
Script:
#!/bin/bash
# Search for NestedWriterVisitorCallback interface definition
ast-grep --pattern 'interface NestedWriterVisitorCallback {
$$$
}'
Length of output: 70
Script:
#!/bin/bash
# Search for NestedWriterVisitorCallback type definition
rg -A 10 "type NestedWriterVisitorCallback"
Length of output: 1005
Script:
#!/bin/bash
# Search for NestedWriteVisitorContext type definition
rg -A 5 "type NestedWriteVisitorContext"
Length of output: 1125
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/runtime/src/enhancements/node/create-enhancement.ts (1)
133-135
: Consider enhancing encryption options validationWhile the basic existence check is good, consider adding more detailed validation of the encryption options structure to catch configuration issues early.
Example validation to consider:
if (!options.encryption) { throw new Error('Encryption options are required for @encrypted enhancement'); } +if ('type' in options.encryption) { + if (!['simple', 'custom'].includes(options.encryption.type)) { + throw new Error('Invalid encryption type. Must be either "simple" or "custom"'); + } + if (options.encryption.type === 'simple' && !options.encryption.secret) { + throw new Error('Secret is required for simple encryption'); + } + if (options.encryption.type === 'custom' && typeof options.encryption.encrypt !== 'function') { + throw new Error('encrypt function is required for custom encryption'); + } +}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/runtime/src/enhancements/node/create-enhancement.ts
(3 hunks)
🔇 Additional comments (4)
packages/runtime/src/enhancements/node/create-enhancement.ts (4)
17-17
: LGTM: Import follows consistent style
The import of withEncrypted
is correctly placed alongside other enhancement imports.
24-24
: LGTM: Enhancement kind addition is appropriate
Adding 'encrypted' to ALL_ENHANCEMENTS
is correct as the enhancement is only applied when @encrypted
attributes are present in the model fields, as confirmed in previous discussions.
104-104
: LGTM: Field detection follows established pattern
The detection of @encrypted
fields follows the same pattern as other enhancements, ensuring consistency in the codebase.
125-139
: LGTM: Correct enhancement ordering with clear documentation
The enhancement is correctly placed before the policy enhancement, with a clear comment explaining why this ordering is necessary (field length considerations). This follows the same pattern as the password enhancement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
packages/runtime/src/enhancements/node/encrypted.ts (4)
17-32
: Enhance function documentation and visibility.The
withEncrypted
function is marked as@private
but is exported. Consider either:
- Removing the
@private
tag if this is intended to be a public API- Adding comprehensive documentation including:
- Parameter descriptions
- Return value description
- Usage examples
- Encryption requirements
44-52
: Enhance error handling and type safety in encryption options validation.The validation logic could be improved with:
- More descriptive error messages including expected vs actual values
- Type checking for
encryptionKey
beingUint8Array
- if (!options.encryption) throw new Error('Encryption options must be provided'); + if (!options.encryption) { + throw new Error('Encryption options must be provided for @encrypted enhancement'); + } if (this.isCustomEncryption(options.encryption!)) { if (!options.encryption.encrypt || !options.encryption.decrypt) - throw new Error('Custom encryption must provide encrypt and decrypt functions'); + throw new Error('Custom encryption requires both encrypt and decrypt functions to be implemented'); } else { - if (!options.encryption.encryptionKey) throw new Error('Encryption key must be provided'); - if (options.encryption.encryptionKey.length !== 32) throw new Error('Encryption key must be 32 bytes'); + if (!options.encryption.encryptionKey) { + throw new Error('Encryption key must be provided for default encryption'); + } + if (!(options.encryption.encryptionKey instanceof Uint8Array)) { + throw new Error('Encryption key must be a Uint8Array'); + } + if (options.encryption.encryptionKey.length !== 32) { + throw new Error(`Encryption key must be 32 bytes, got ${options.encryption.encryptionKey.length} bytes`); + } }
55-57
: Consider using key derivation for enhanced security.Instead of directly using the raw key, consider implementing PBKDF2 or similar key derivation function to strengthen the encryption key.
private async getKey(secret: Uint8Array): Promise<CryptoKey> { - return crypto.subtle.importKey('raw', secret, 'AES-GCM', false, ['encrypt', 'decrypt']); + // First derive a key using PBKDF2 + const salt = new Uint8Array(16); // Use a consistent salt or store it with the data + const baseKey = await crypto.subtle.importKey( + 'raw', + secret, + 'PBKDF2', + false, + ['deriveBits', 'deriveKey'] + ); + return crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt, + iterations: 100000, + hash: 'SHA-256', + }, + baseKey, + { name: 'AES-GCM', length: 256 }, + false, + ['encrypt', 'decrypt'] + ); }
111-117
: Improve null safety in preprocessArgs.Use optional chaining and add type safety.
- if (args && args.data && actionsOfInterest.includes(action)) { + if (args?.data && actionsOfInterest.includes(action)) { await this.preprocessWritePayload(this.model, action as PrismaWriteActionType, args); } return args;🧰 Tools
🪛 Biome (1.9.4)
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/runtime/src/enhancements/node/encrypted.ts
(1 hunks)tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/integration/tests/enhancements/with-encrypted/with-encrypted.test.ts
🧰 Additional context used
🪛 Biome (1.9.4)
packages/runtime/src/enhancements/node/encrypted.ts
[error] 113-113: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for working on this great feature and the patience for the review process @genu 😄. I'm approving and merging it. It'll be part of the v2.11.0 release!
Resolves #1643