fix: Tenant ID Filter Tightening - #141
Conversation
📝 WalkthroughWalkthroughRefactors encryption from a MikroORM event-subscriber into a new EncryptedType with global encryptor registration, deterministic-IV (v2) encryption, tenant-scoped context; wires encryptors into Redis TTL cache and S3 object store, updates templates/blueprints/CLI for ENCRYPTION_KEY, and bumps many package versions and tests. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Req as Request/Middleware
participant EM as MikroORM/EntityManager
participant Type as EncryptedType
participant Encr as FieldEncryptor
participant Redis as RedisTtlCache
participant S3 as S3ObjectStore
Req->>EM: setFilterParams(tenantId)
Req->>Type: setEncryptionTenantId(tenantId)
Note right of Type: AsyncLocalStorage tenant context
EM->>Type: convertToDatabaseValue(plaintext)
Type->>Encr: encrypt(tenantDerivedKey, plaintext)
Encr-->>Type: ciphertext (v2:...)
Type->>EM: store ciphertext
EM->>Redis: write(cacheKey, value)
Redis->>Encr: encrypt value (if enabled)
Encr-->>Redis: encrypted cache payload
EM->>S3: putObject(key, body)
S3->>Encr: encrypt body (if enabled)
Encr-->>S3: encrypted object body
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
framework/core/src/persistence/fieldEncryptor.ts (1)
82-113:⚠️ Potential issue | 🔴 Critical
v2needs a backfill or dual-read query path before rollout.
decrypt()stays backward-compatible, but any equality filter or uniqueness check that derives its probe value viaencrypt()now produces onlyv2:ciphertext. Existingv1:rows will never compare equal, so tenant/customer filters can silently miss legacy records until those values are rewritten.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/core/src/persistence/fieldEncryptor.ts` around lines 82 - 113, The new v2 deterministic encrypt() breaks equality/unique checks for existing v1 rows; add a compatibility path: extend FieldEncryptor.encrypt to accept an optional version flag (e.g., encrypt(plaintext, tenantId?, version?: 'v1'|'v2')) and implement a small legacy path that reproduces the v1 probe (or call a new helper legacyEncrypt/legacyIv that uses the old IV derivation), and update any query code that builds equality/unique probes to try both versions (generate both v2 and v1 ciphertexts via encrypt(...,'v2') and encrypt(...,'v1') or use decrypt fallback) so existing v1 rows are matched until you perform a backfill; reference encrypt, decrypt, deriveDeterministicIv (and add legacyDeriveIv or legacyEncrypt helper) when making the changes.
♻️ Duplicate comments (1)
framework/testing/CHANGELOG.md (1)
3-8:⚠️ Potential issue | 🟠 MajorSame semantic versioning concern as in framework/common/CHANGELOG.md.
The "Encryptor required on redis and s3" entry carries the same semantic versioning concern as noted in the framework/common changelog review. If this makes a previously optional parameter mandatory, it should be a major version bump rather than a patch.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/testing/CHANGELOG.md` around lines 3 - 8, The changelog entry "Encryptor required on redis and s3" under header "## 1.2.6" appears to describe a breaking change (making an optional parameter mandatory); change the release header from "## 1.2.6" to a major bump (e.g., "## 2.0.0") and mark the entry as a Breaking Change (or move it to a "Breaking Changes" section), or alternatively revise the entry to state that the encryptor remains optional if you intend a patch—update the header and the entry text accordingly so the version and wording correctly reflect whether the change is breaking.
🧹 Nitpick comments (19)
blueprint/interfaces/worker/CHANGELOG.md (1)
3-20: Consider using more specific patch notes for traceability.Entries like “upgrade packages” / “package bumps” make it harder to map release intent later. A short list of key bumped packages or affected modules would improve auditability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blueprint/interfaces/worker/CHANGELOG.md` around lines 3 - 20, The CHANGELOG entries under headings like "## 1.0.13", "## 1.0.12", and "## 1.0.11" are too generic ("upgrade packages", "package bumps"); update each release note to list the specific packages or modules updated (e.g., package names and new versions), include any important affected components or breaking changes, and optionally reference PR/issue numbers for traceability so future readers can map intent to changes.blueprint/interfaces/billing/CHANGELOG.md (1)
3-20: Changelog format is consistent; consider adding concrete change detail.The version blocks are correctly structured. Optional improvement: mention specific package/version deltas to make regressions easier to trace.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blueprint/interfaces/billing/CHANGELOG.md` around lines 3 - 20, The changelog entries for the version headers (e.g., "## 1.0.13", "## 1.0.12", "## 1.0.11") are too generic; update each version block to list concrete changes by naming the packages and their new versions (or the semver delta) and a short note on impact (e.g., "bumped lodash 4.17.21 -> 4.17.22: patch security fix"), so readers can trace regressions easily.blueprint/billing-base/registrations.ts (1)
1-27: Reorder imports to match the required layering.Line 1-27 places external dependencies (
@mikro-orm/*) after Forklaunch framework imports. Please move external imports above the@forklaunch/*group.♻️ Suggested import-order adjustment
+import { ForkOptions } from '@mikro-orm/core'; +import { EntityManager, MikroORM } from '@mikro-orm/postgresql'; import { number, optional, schemaValidator, SchemaValidator, string } from '@forklaunch/blueprint-core'; import { Metrics, metrics } from '@forklaunch/blueprint-monitoring'; import { OpenTelemetryCollector } from '@forklaunch/core/http'; import { FieldEncryptor } from '@forklaunch/core/persistence'; @@ -import { ForkOptions } from '@mikro-orm/core'; -import { EntityManager, MikroORM } from '@mikro-orm/postgresql';As per coding guidelines, "Organize imports in 7 layers: (1) Node built-ins with node: prefix, (2) External dependencies, (3) Forklaunch framework packages, (4) Cross-module imports, (5) Local persistence, (6) Local domain, (7) Same directory".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blueprint/billing-base/registrations.ts` around lines 1 - 27, Imports in registrations.ts are out of the mandated layering: external packages (`@mikro-orm/core`, `@mikro-orm/postgresql`) are listed after Forklaunch framework imports; reorder imports so external dependencies appear before any `@forklaunch/`* imports. Locate the import block at the top of registrations.ts (symbols to find: imports of MikroORM, EntityManager, ForkOptions) and move those external imports (e.g., from '@mikro-orm/core' and '@mikro-orm/postgresql') up above the group of `@forklaunch/`* imports, keeping the rest of the groups intact and preserving existing relative order within each layer.framework/infrastructure/redis/__test__/redisTtlCache.test.ts (1)
28-31: Add at least one encryption-enabled Redis round-trip test.The cache instance at lines 28-31 sets
disabled: true, so the encryption path is never exercised. Add a test withdisabled: falseto verify the encryption integration works end-to-end.Example test addition
+ test('encryptor-enabled round trip', async () => { + const encryptedCache = new RedisTtlCache( + 5000, + new OpenTelemetryCollector('test'), + { url: `redis://${container.getHost()}:${container.getMappedPort(6379)}` }, + { enabled: true, level: 'info' }, + { + encryptor: new FieldEncryptor('test-encryption-key-for-redis-tests'), + disabled: false + } + ); + + await encryptedCache.putRecord({ key: 'enc_key', value: { ok: true }, ttlMilliseconds: 1000 }); + await expect(encryptedCache.readRecord('enc_key')).resolves.toMatchObject({ + key: 'enc_key', + value: { ok: true } + }); + await encryptedCache.disconnect(); + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/infrastructure/redis/__test__/redisTtlCache.test.ts` around lines 28 - 31, The current test uses FieldEncryptor with disabled: true so the encryption path isn't exercised; add a new test in redisTtlCache.test.ts that creates the cache with encryptor: new FieldEncryptor('test-encryption-key-for-redis-tests') and disabled: false, then perform an end-to-end round-trip: await cache.set('some-key', someValue, ttl) and await cache.get('some-key') and assert the retrieved value equals the original to verify encryption/decryption works; ensure the test cleans up (flush/close) after itself.framework/infrastructure/S3/__test__/s3ObjectStore.test.ts (1)
1-6: Normalize imports tonode:prefix + required layer order.
Readableshould be imported as a Node built-in (node:stream) and grouped before external/Forklaunch imports.As per coding guidelines, “Organize imports in 7 layers: (1) Node built-ins with node: prefix, (2) External dependencies, (3) Forklaunch framework packages, (4) Cross-module imports, (5) Local persistence, (6) Local domain, (7) Same directory”.Suggested import normalization
+import { Readable } from 'node:stream'; + import { S3Client } from '@aws-sdk/client-s3'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import { OpenTelemetryCollector } from '@forklaunch/core/http'; import { FieldEncryptor } from '@forklaunch/core/persistence'; -import { Readable } from 'stream'; -import { beforeEach, describe, expect, it, vi } from 'vitest'; import { S3ObjectStore } from '../index';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/infrastructure/S3/__test__/s3ObjectStore.test.ts` around lines 1 - 6, Import ordering and source for Readable is incorrect: change the Readable import to use the Node built-in with the node: prefix (import Readable from 'node:stream') and reorder imports so Node built-ins (Readable) come first, then external deps (S3Client), then Forklaunch framework packages (OpenTelemetryCollector, FieldEncryptor), and finally the local module import (S3ObjectStore) to follow the project's 7-layer convention; update the import statements around S3Client, OpenTelemetryCollector, FieldEncryptor, Readable, and S3ObjectStore accordingly.blueprint/iam-base/mikro-orm.config.ts (1)
1-15: Reorder imports to match the required layer sequence.Current ordering places Forklaunch framework imports before external dependencies.
As per coding guidelines, “Organize imports in 7 layers: (1) Node built-ins with node: prefix, (2) External dependencies, (3) Forklaunch framework packages, (4) Cross-module imports, (5) Local persistence, (6) Local domain, (7) Same directory”.Suggested import ordering
-import { number, schemaValidator, string } from '@forklaunch/blueprint-core'; - -import { - FieldEncryptor, - registerEncryptor -} from '@forklaunch/core/persistence'; -import { - createConfigInjector, - getEnvVar, - Lifetime -} from '@forklaunch/core/services'; import { Migrator } from '@mikro-orm/migrations'; import { defineConfig, Platform, TextType, Type } from '@mikro-orm/postgresql'; import dotenv from 'dotenv'; + +import { number, schemaValidator, string } from '@forklaunch/blueprint-core'; +import { FieldEncryptor, registerEncryptor } from '@forklaunch/core/persistence'; +import { createConfigInjector, getEnvVar, Lifetime } from '@forklaunch/core/services'; import * as entities from './persistence/entities';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blueprint/iam-base/mikro-orm.config.ts` around lines 1 - 15, The import statements in mikro-orm.config.ts are out of the mandated 7-layer order: external deps should come before Forklaunch framework packages and local persistence; reorder the imports so they follow the layers: (1) Node built-ins (if any), (2) external dependencies (dotenv, `@mikro-orm/`*), (3) Forklaunch framework packages (`@forklaunch/core/`* and `@forklaunch/blueprint-core`), (4) cross-module imports (createConfigInjector, getEnvVar, Lifetime references), (5) local persistence (import * as entities), (6) local domain, (7) same-directory; specifically move dotenv and { defineConfig, Platform, TextType, Type } from '@mikro-orm/postgresql' and { Migrator } into the external block above the Forklaunch imports (number, schemaValidator, string; FieldEncryptor, registerEncryptor; createConfigInjector, getEnvVar, Lifetime) and keep the entities import in the local persistence block so the file adheres to the required import-layer ordering.framework/core/__test__/fieldEncryptor.test.ts (2)
86-91: Misleading test description.The test description says "unknown version prefix" but the ciphertext
'v2:abc:def:ghi'uses the validv2:prefix. The test is actually validating that malformed ciphertext structure (invalid base64 segments) throwsDecryptionError, not an unknown version prefix. Consider updating the description for clarity.📝 Proposed fix for test description
- it('should throw DecryptionError for unknown version prefix', () => { + it('should throw DecryptionError for malformed ciphertext structure', () => { const enc = new FieldEncryptor(MASTER_KEY); expect(() => enc.decrypt('v2:abc:def:ghi', 'tenant-a')).toThrow( DecryptionError ); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/core/__test__/fieldEncryptor.test.ts` around lines 86 - 91, The test description is misleading: update the it(...) description for the test that constructs new FieldEncryptor(MASTER_KEY) and calls enc.decrypt('v2:abc:def:ghi', 'tenant-a') to state that it expects a DecryptionError for malformed ciphertext/invalid base64 segments rather than "unknown version prefix"; keep the assertion and DecryptionError check unchanged so the test verifies decrypt throws for invalid ciphertext structure.
39-45: Duplicate test case.This test (lines 39-45) tests the same behavior as lines 23-29 — both verify that the same plaintext encrypted under different tenant IDs yields different ciphertext. Consider removing one to avoid redundant test coverage.
🧹 Proposed fix to remove duplicate test
- it('should produce different ciphertext for same plaintext with different tenants', () => { - const enc = new FieldEncryptor(MASTER_KEY); - const plaintext = 'same-data'; - const ct1 = enc.encrypt(plaintext, 'tenant-a'); - const ct2 = enc.encrypt(plaintext, 'tenant-b'); - expect(ct1).not.toBe(ct2); - });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/core/__test__/fieldEncryptor.test.ts` around lines 39 - 45, Remove the duplicate test that validates different ciphertexts for the same plaintext under different tenants: locate the second test block with the description "should produce different ciphertext for same plaintext with different tenants" that instantiates FieldEncryptor with MASTER_KEY and calls enc.encrypt(plaintext, 'tenant-a') and enc.encrypt(plaintext, 'tenant-b') (this duplicates the earlier test around lines 23-29) and delete that redundant it(...) block so only one test asserting ct1 !== ct2 remains.cli/src/core/ast/infrastructure/redis.rs (1)
72-80: No action required — FieldEncryptor and ENCRYPTION_KEY are properly injected by parent transformations.The
FieldEncryptorimport andENCRYPTION_KEYenvironment variable are not registered within theredis.rsmodule functions themselves. However, this is by design: bothtransform_service_to_worker.rsandtransform_registrations_ts.rshandle these injections separately for non-database workers before or after calling the redis utilities. The generated TypeScript code correctly includes both the import statement and environment configuration.To improve maintainability, consider documenting that
redis_ttl_cache_runtime_dependency()relies on the parent transformation to provide these prerequisites, or refactor to make the function more self-contained.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/core/ast/infrastructure/redis.rs` around lines 72 - 80, The redis module currently assumes FieldEncryptor import and ENCRYPTION_KEY are injected by parent transforms; update redis_ttl_cache_runtime_dependency (or the factory that constructs RedisTtlCache) to explicitly document this prerequisite and/or make it self-contained by accepting an encryptor or encryptionKey parameter (e.g., add an optional encryptor/ encryptionKey argument to redis_ttl_cache_runtime_dependency and pass it into the factory that creates new RedisTtlCache) and update the function signature and callers (transform_service_to_worker.rs and transform_registrations_ts.rs) to provide the dependency if present.cli/src/templates/project/service/mikro-orm.config.ts (1)
1-9: Reorder this import block to the standard 7 layers.
@mikro-orm/*anddotenvbelong in the external-dependency layer and should come before the@forklaunch/*imports.As per coding guidelines, "Organize imports in 7 layers: (1) Node built-ins with node: prefix, (2) External dependencies, (3) Forklaunch framework packages, (4) Cross-module imports, (5) Local persistence, (6) Local domain, (7) Same directory".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/templates/project/service/mikro-orm.config.ts` around lines 1 - 9, Reorder the import block to follow the 7-layer convention: place external dependencies first (move imports for Migrator from '@mikro-orm/*', defineConfig from '@mikro-orm/{{database}}', and dotenv) before the Forklaunch framework imports; then keep Forklaunch framework packages (createConfigInjector, getEnvVar, Lifetime from '@forklaunch/core/services' and FieldEncryptor/registerEncryptor from '@forklaunch/core/persistence'); then cross-module imports (number, SchemaValidator, string from '@{{app_name}}/core'); followed by local persistence imports (import * as entities from './persistence/entities'); ensure conditional imports like Platform/TextType/Type remain in the same external layer when present, and preserve relative ordering and blank lines between each of the 7 layers.blueprint/billing-stripe/registrations.ts (1)
1-29: Reorder this import block to match the repo’s 7-layer convention.
@mikro-orm/*andstripeare external dependencies, so they should sit above the@forklaunch/*imports instead of below them.As per coding guidelines, "Organize imports in 7 layers: (1) Node built-ins with node: prefix, (2) External dependencies, (3) Forklaunch framework packages, (4) Cross-module imports, (5) Local persistence, (6) Local domain, (7) Same directory".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@blueprint/billing-stripe/registrations.ts` around lines 1 - 29, Reorder the import block to follow the repo’s 7-layer convention by moving external dependencies (e.g., Stripe and `@mikro-orm/`* like Stripe from 'stripe' and { ForkOptions } from '@mikro-orm/core' / { EntityManager, MikroORM } from '@mikro-orm/postgresql') above the Forklaunch framework imports (those starting with '@forklaunch/*'); keep the rest of the existing imports (OpenTelemetryCollector, FieldEncryptor, ComplianceDataService, createConfigInjector, StripeBillingPortalService, RedisTtlCache, etc.) in their original relative order after the external deps so the file-level imports match the repo guideline.cli/src/templates/project/service/registrations.ts (2)
154-159:ENCRYPTION_KEYmay be unused in some configurations.Similar to the import,
ENCRYPTION_KEYis always defined but only consumed when Redis cache, S3, or non-database workers are enabled. If none apply, this creates an unused config entry and requires an environment variable that won't be used.Consider wrapping this configuration in the appropriate conditional blocks to avoid requiring unnecessary environment variables.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/templates/project/service/registrations.ts` around lines 154 - 159, ENCRYPTION_KEY is always registered via the ENCRYPTION_KEY binding (Lifetime.Singleton, value: getEnvVar('ENCRYPTION_KEY')) even though it's only needed when Redis cache, S3, or non-database workers are enabled; move or wrap this registration in the same conditional blocks used for those features so the container only defines ENCRYPTION_KEY when isRedisEnabled || isS3Enabled || isNonDbWorker (or equivalent flags) is true, ensuring getEnvVar('ENCRYPTION_KEY') is only called/required when one of those features is active.
12-13: Potential unused import in generated code.
FieldEncryptoris imported unconditionally but is only used whenis_request_cache_needed,is_s3_enabled, or (is_worker&&!is_database_worker) is true. If none of these conditions apply, the generated code will have an unused import.Consider wrapping the import in a conditional:
{{`#is_encryption_needed`}}import { FieldEncryptor } from "@forklaunch/core/persistence";{{/is_encryption_needed}}Where
is_encryption_neededis derived from the relevant feature flags.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/templates/project/service/registrations.ts` around lines 12 - 13, The import of FieldEncryptor is unconditional in the template (registrations.ts) but only used when request cache, S3, or worker-without-database features are enabled; change the template to conditionally emit the import by adding a new composite flag (e.g., is_encryption_needed) that is true when is_request_cache_needed || is_s3_enabled || (is_worker && !is_database_worker), then wrap the existing import line "import { FieldEncryptor } from \"@forklaunch/core/persistence\";" in a mustache conditional block ({{`#is_encryption_needed`}}...{{/is_encryption_needed}}) so the import only appears when FieldEncryptor is actually used.framework/infrastructure/S3/index.ts (2)
116-124: Silent decryption failure may mask data corruption.When decryption fails (e.g., wrong tenant key, corrupted ciphertext), the method silently returns the raw encrypted value. This could lead to downstream JSON parse errors or data integrity issues that are hard to diagnose.
Consider logging decryption failures or providing a configuration option to throw on decryption errors in non-production environments.
♻️ Proposed improvement
private decryptBody(body: string): string { if (!this.encryptor || this.encryptionDisabled) return body; if (!isEncrypted(body)) return body; try { return this.encryptor.decrypt(body, getCurrentTenantId()) ?? body; } catch { + // Log decryption failure for debugging - this may indicate key mismatch or corruption + this.openTelemetryCollector.warn('S3 object decryption failed, returning raw value'); return body; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/infrastructure/S3/index.ts` around lines 116 - 124, The decryptBody method currently swallows all decryption failures and returns the raw encrypted payload; update decryptBody to catch errors from this.encryptor.decrypt and: 1) log the error (including the tenant id from getCurrentTenantId() and the fact the input was encrypted) using the module/logger used elsewhere so failures are visible, and 2) make behavior configurable (e.g., an instance flag like encryptionDisabled already exists or a new throwOnDecryptionError option) so in non-production/tests the method re-throws the error instead of returning the raw value; ensure you still check this.encryptor, encryptionDisabled, and isEncrypted before attempting decryption and preserve the fallback to returning body only when configured to do so.
154-160: ContentType mismatch: encrypted body stored as 'application/json'.The body is now encrypted ciphertext (a string like
v2:...), butContentTypeis still set to'application/json'. This is technically incorrect for encrypted data and could confuse debugging or S3 object inspection tools.Consider using a custom content type or metadata to indicate encryption status.
♻️ Suggested fix
const params: PutObjectCommandInput = { Bucket: this.bucket, Key: key, Body: body, - ContentType: 'application/json' + ContentType: this.encryptionDisabled ? 'application/json' : 'application/octet-stream', + Metadata: this.encryptionDisabled ? undefined : { encrypted: 'true' } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/infrastructure/S3/index.ts` around lines 154 - 160, The S3 upload currently sets ContentType to 'application/json' even though the body is encrypted via encryptBody; update the PutObjectCommandInput construction in the method that builds params (where encryptBody and const params are used) to reflect encrypted content—e.g., set ContentType to a generic type like 'application/octet-stream' or a custom type such as 'application/vnd.<app>-encrypted' and add metadata flags (e.g., Metadata: { encrypted: 'true', original-content-type: 'application/json' }) so callers and tools know the object is encrypted and the original type is preserved.framework/infrastructure/redis/index.ts (1)
87-102: Consistent with S3ObjectStore, but same silent failure concern.The encryption/decryption helpers mirror the S3ObjectStore implementation, which is good for consistency. However, the same concern applies: silent decryption failures could mask issues.
Consider adding observability (logging/metrics) when decryption fails to aid debugging.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/infrastructure/redis/index.ts` around lines 87 - 102, The decryptValue/encryptValue helpers currently swallow decryption errors; update decryptValue (and optionally encryptValue) to emit observability when decryption fails or when an unexpected condition occurs: capture the exception in decryptValue and call the module logger/metrics (e.g., processLogger.error or a provided logger/metrics client) with context including the function name decryptValue, the tenant id from getCurrentTenantId(), and a safely redacted snippet or indication of the value (use isEncrypted(value) to gate logging); ensure you still return the original value on failure but record the error so failures are observable. Reference encryptValue, decryptValue, isEncrypted, encryptor, and getCurrentTenantId when locating the code to modify.framework/core/__test__/complianceEventSubscriber.test.ts (1)
38-42: Consider isolating global encryptor state between tests.
registerEncryptormodifies module-level state (_encryptor). If other test files in the same test run also callregisterEncryptor, there could be test pollution. Consider adding anafterEachorafterAllto reset the encryptor state, or exposing aclearEncryptor()helper for test cleanup.♻️ Suggested cleanup pattern
+ import { registerEncryptor, clearEncryptor } from '../src/persistence/encryptedType'; + // ... or add a clearEncryptor export if not available beforeEach(() => { const encryptor = new FieldEncryptor(MASTER_KEY); registerEncryptor(encryptor); encryptedType = new EncryptedType('string'); }); + afterEach(() => { + // Reset global state to prevent test pollution + registerEncryptor(undefined as any); // or clearEncryptor() if available + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/core/__test__/complianceEventSubscriber.test.ts` around lines 38 - 42, Tests registerEncryptor in beforeEach which mutates module-level _encryptor and can leak state across tests; add cleanup to reset that global after each test by either calling an exported clearEncryptor() helper (implement and export clearEncryptor that sets _encryptor = undefined) or invoking a provided reset function in an afterEach/afterAll; update the test file (around the beforeEach block where FieldEncryptor and EncryptedType are created) to call clearEncryptor() in afterEach to isolate state between tests.framework/core/src/persistence/encryptedType.ts (1)
153-169: Number deserialization could return NaN for corrupted data.
Number(value)returnsNaNfor non-numeric strings. If encrypted data is corrupted or decryption partially fails, this could silently produceNaNvalues in the application.Consider adding validation:
♻️ Suggested validation
case 'number': - return Number(value); + const num = Number(value); + if (Number.isNaN(num)) { + // Log warning for debugging + return null; + } + return num;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/core/src/persistence/encryptedType.ts` around lines 153 - 169, In deserializeValue, when originalType === 'number' validate the parsed numeric result instead of blindly returning Number(value); call Number(value), check for isFinite/Number.isNaN (or Number.isFinite) and handle invalid parsing by throwing or returning a safe fallback (e.g., null or the raw string) to avoid silently producing NaN — update the number branch in deserializeValue to perform this validation and return the chosen fallback or raise an error so callers don't receive NaN.framework/core/src/persistence/tenantFilter.ts (1)
37-42: Consider exporting setTenantContext middleware for automatic HTTP route protection.The skip filtering behavior when
args?.tenantIdis falsy (lines 37-42) is intentional for startup, background jobs, and other non-request contexts. However,setTenantContextmiddleware inframework/core/src/http/middleware/request/tenantContext.middleware.tsis not exported from the http module's public API, so applications can't easily apply it to guarantee tenant context is set for protected/authenticated routes before queries execute.For protected/authenticated routes, the middleware auto-returns 403 if tenantId is missing—preventing the fail-open scenario. Export this middleware and document its usage to ensure developers can leverage this built-in protection rather than manually managing tenant context in each handler.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@framework/core/src/persistence/tenantFilter.ts` around lines 37 - 42, Export the setTenantContext middleware from the http module's public API so applications can easily apply it to protected routes; specifically, add a re-export of the setTenantContext symbol (defined in tenantContext.middleware.ts) from the http module's top-level/barrel export (the http index that currently exposes middleware utilities) so consumers can import it directly (e.g., from the http package) and apply it to authenticated routes to auto-403 when args.tenantId is missing. Ensure the exported symbol name is exactly setTenantContext and update any module export lists or index.ts/barrel files accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@blueprint/iam-base/mikro-orm.config.ts`:
- Around line 62-64: registerEncryptor is being called at module scope and can
silently overwrite previous encryptors; update the registerEncryptor
implementation in framework/core/src/persistence/encryptedType.ts to enforce
idempotency: detect if an encryptor is already registered and either (a) throw
an error, (b) emit a clear warning, or (c) validate that the new value is the
same instance before returning — pick one policy, implement it (e.g., store the
first instance in a private variable and compare references on subsequent
calls), and update any callers like mikro-orm.config.ts (which currently calls
registerEncryptor(new
FieldEncryptor(validConfigInjector.resolve(tokens.ENCRYPTION_KEY)))) to expect
the new behavior. Ensure the check uses the existing registerEncryptor symbol
and preserves current behavior when the same instance is re-registered.
In `@blueprint/implementations/worker/bullmq/CHANGELOG.md`:
- Around line 27-32: The 1.0.12 changelog entry is too generic; update the "##
1.0.12" section (the heading and the "- update fl packages" bullet) to list
explicit package names and exact versions that were updated (e.g., include
entries like "@forklaunch/interfaces-worker vX.Y.Z" and any other changed fl
packages), matching the style used in nearby releases so audits can see exact
dependency changes; ensure the bullet points enumerate each package and version
and keep formatting consistent with other changelog entries.
In `@blueprint/implementations/worker/kafka/CHANGELOG.md`:
- Around line 27-32: The 1.0.12 changelog entry under "## 1.0.12" currently says
only "update fl packages"; update it to list the specific dependency bumps like
the other releases do (i.e., name each fl package and its new version), editing
the "update fl packages" line in the CHANGELOG.md so the entry includes concrete
package names and version numbers for consistency with prior entries.
In `@framework/common/CHANGELOG.md`:
- Around line 3-8: The changelog claims a patch release ("1.2.6") but the change
"Encryptor required on redis and s3" is breaking because it makes the
previously-optional encryption parameter mandatory; either relax the constructor
signatures back to accept an optional encryption parameter (e.g., in the S3
constructor in framework/infrastructure/S3/index.ts and the Redis client
constructor) or, if the required change is intended, update the release metadata
and CHANGELOG entry to a major version bump (e.g., 2.0.0) and note the breaking
change; locate and adjust the relevant constructor signatures or bump the
version and changelog heading accordingly.
In `@framework/core/src/http/telemetry/openTelemetryCollector.ts`:
- Around line 161-165: getMetric currently force-casts this._metrics[metricId]
which can be undefined and causes downstream crashes; update the getMetric
method to check whether this._metrics[metricId] is present and, if missing,
throw a descriptive error (e.g., `Metric "${String(metricId)}" not registered`)
or return a properly typed undefined/optional value depending on API
expectations; locate the getMetric function and the _metrics map in
openTelemetryCollector.ts and replace the direct cast with a guarded check that
either throws the error including metricId or returns a safe optional result
before returning the MetricType.
In `@framework/core/src/persistence/fieldEncryptor.ts`:
- Around line 67-79: The deriveDeterministicIv function currently generates a
deterministic IV (nonce) via HMAC-SHA256 which causes IV reuse with AES-256-GCM
(function deriveDeterministicIv, constant IV_BYTES) and is insecure; replace
this design by removing deterministic IV derivation for AES-GCM and instead
implement a safe alternative: either switch encryption to a
nonce-misuse-resistant AEAD (e.g., AES-SIV / AES-GCM-SIV) for
deterministic/searchable needs, or keep AES-GCM but generate a cryptographically
random IV per encryption and implement a separate deterministic lookup mechanism
(e.g., HMAC-based blind index) that uses a separate key and includes field/store
domain in the HMAC input to avoid cross-store linkability; update callers of
deriveDeterministicIv (and any code assuming deterministic IVs) to use the new
AEAD or to store the blind index alongside the random-IV ciphertext.
In `@framework/express/CHANGELOG.md`:
- Line 41: Replace the vague changelog entry "up packages" with a clear,
explicit release note: locate the line containing the exact phrase "up packages"
in CHANGELOG.md and change it to a descriptive entry such as "chore: bump
dependencies — updated [packageA]@x.y.z, [packageB]@a.b.c to address
security/compatibility" (or similar wording that lists the updated packages and
the reason for the bump); ensure the new line follows the project's changelog
style and includes which packages were updated and why.
In `@framework/validator/CHANGELOG.md`:
- Around line 23-24: Replace the ambiguous changelog entries "up packages" and
"update packages" with concrete, consumer-facing notes: identify the exact
packages and target versions or intent (e.g., "Bump lodash to 4.17.21 to fix
CVE-XXXX" or "Upgrade react to ^18.2.0 for breaking API X") for the entries
currently labeled "up packages" (line marked in the diff) and "update packages"
(also noted); update the CHANGELOG.md entries so each line contains the package
name(s), version change or range, and a one-line rationale/impact statement.
---
Outside diff comments:
In `@framework/core/src/persistence/fieldEncryptor.ts`:
- Around line 82-113: The new v2 deterministic encrypt() breaks equality/unique
checks for existing v1 rows; add a compatibility path: extend
FieldEncryptor.encrypt to accept an optional version flag (e.g.,
encrypt(plaintext, tenantId?, version?: 'v1'|'v2')) and implement a small legacy
path that reproduces the v1 probe (or call a new helper legacyEncrypt/legacyIv
that uses the old IV derivation), and update any query code that builds
equality/unique probes to try both versions (generate both v2 and v1 ciphertexts
via encrypt(...,'v2') and encrypt(...,'v1') or use decrypt fallback) so existing
v1 rows are matched until you perform a backfill; reference encrypt, decrypt,
deriveDeterministicIv (and add legacyDeriveIv or legacyEncrypt helper) when
making the changes.
---
Duplicate comments:
In `@framework/testing/CHANGELOG.md`:
- Around line 3-8: The changelog entry "Encryptor required on redis and s3"
under header "## 1.2.6" appears to describe a breaking change (making an
optional parameter mandatory); change the release header from "## 1.2.6" to a
major bump (e.g., "## 2.0.0") and mark the entry as a Breaking Change (or move
it to a "Breaking Changes" section), or alternatively revise the entry to state
that the encryptor remains optional if you intend a patch—update the header and
the entry text accordingly so the version and wording correctly reflect whether
the change is breaking.
---
Nitpick comments:
In `@blueprint/billing-base/registrations.ts`:
- Around line 1-27: Imports in registrations.ts are out of the mandated
layering: external packages (`@mikro-orm/core`, `@mikro-orm/postgresql`) are listed
after Forklaunch framework imports; reorder imports so external dependencies
appear before any `@forklaunch/`* imports. Locate the import block at the top of
registrations.ts (symbols to find: imports of MikroORM, EntityManager,
ForkOptions) and move those external imports (e.g., from '@mikro-orm/core' and
'@mikro-orm/postgresql') up above the group of `@forklaunch/`* imports, keeping
the rest of the groups intact and preserving existing relative order within each
layer.
In `@blueprint/billing-stripe/registrations.ts`:
- Around line 1-29: Reorder the import block to follow the repo’s 7-layer
convention by moving external dependencies (e.g., Stripe and `@mikro-orm/`* like
Stripe from 'stripe' and { ForkOptions } from '@mikro-orm/core' / {
EntityManager, MikroORM } from '@mikro-orm/postgresql') above the Forklaunch
framework imports (those starting with '@forklaunch/*'); keep the rest of the
existing imports (OpenTelemetryCollector, FieldEncryptor, ComplianceDataService,
createConfigInjector, StripeBillingPortalService, RedisTtlCache, etc.) in their
original relative order after the external deps so the file-level imports match
the repo guideline.
In `@blueprint/iam-base/mikro-orm.config.ts`:
- Around line 1-15: The import statements in mikro-orm.config.ts are out of the
mandated 7-layer order: external deps should come before Forklaunch framework
packages and local persistence; reorder the imports so they follow the layers:
(1) Node built-ins (if any), (2) external dependencies (dotenv, `@mikro-orm/`*),
(3) Forklaunch framework packages (`@forklaunch/core/`* and
`@forklaunch/blueprint-core`), (4) cross-module imports (createConfigInjector,
getEnvVar, Lifetime references), (5) local persistence (import * as entities),
(6) local domain, (7) same-directory; specifically move dotenv and {
defineConfig, Platform, TextType, Type } from '@mikro-orm/postgresql' and {
Migrator } into the external block above the Forklaunch imports (number,
schemaValidator, string; FieldEncryptor, registerEncryptor;
createConfigInjector, getEnvVar, Lifetime) and keep the entities import in the
local persistence block so the file adheres to the required import-layer
ordering.
In `@blueprint/interfaces/billing/CHANGELOG.md`:
- Around line 3-20: The changelog entries for the version headers (e.g., "##
1.0.13", "## 1.0.12", "## 1.0.11") are too generic; update each version block to
list concrete changes by naming the packages and their new versions (or the
semver delta) and a short note on impact (e.g., "bumped lodash 4.17.21 ->
4.17.22: patch security fix"), so readers can trace regressions easily.
In `@blueprint/interfaces/worker/CHANGELOG.md`:
- Around line 3-20: The CHANGELOG entries under headings like "## 1.0.13", "##
1.0.12", and "## 1.0.11" are too generic ("upgrade packages", "package bumps");
update each release note to list the specific packages or modules updated (e.g.,
package names and new versions), include any important affected components or
breaking changes, and optionally reference PR/issue numbers for traceability so
future readers can map intent to changes.
In `@cli/src/core/ast/infrastructure/redis.rs`:
- Around line 72-80: The redis module currently assumes FieldEncryptor import
and ENCRYPTION_KEY are injected by parent transforms; update
redis_ttl_cache_runtime_dependency (or the factory that constructs
RedisTtlCache) to explicitly document this prerequisite and/or make it
self-contained by accepting an encryptor or encryptionKey parameter (e.g., add
an optional encryptor/ encryptionKey argument to
redis_ttl_cache_runtime_dependency and pass it into the factory that creates new
RedisTtlCache) and update the function signature and callers
(transform_service_to_worker.rs and transform_registrations_ts.rs) to provide
the dependency if present.
In `@cli/src/templates/project/service/mikro-orm.config.ts`:
- Around line 1-9: Reorder the import block to follow the 7-layer convention:
place external dependencies first (move imports for Migrator from
'@mikro-orm/*', defineConfig from '@mikro-orm/{{database}}', and dotenv) before
the Forklaunch framework imports; then keep Forklaunch framework packages
(createConfigInjector, getEnvVar, Lifetime from '@forklaunch/core/services' and
FieldEncryptor/registerEncryptor from '@forklaunch/core/persistence'); then
cross-module imports (number, SchemaValidator, string from
'@{{app_name}}/core'); followed by local persistence imports (import * as
entities from './persistence/entities'); ensure conditional imports like
Platform/TextType/Type remain in the same external layer when present, and
preserve relative ordering and blank lines between each of the 7 layers.
In `@cli/src/templates/project/service/registrations.ts`:
- Around line 154-159: ENCRYPTION_KEY is always registered via the
ENCRYPTION_KEY binding (Lifetime.Singleton, value: getEnvVar('ENCRYPTION_KEY'))
even though it's only needed when Redis cache, S3, or non-database workers are
enabled; move or wrap this registration in the same conditional blocks used for
those features so the container only defines ENCRYPTION_KEY when isRedisEnabled
|| isS3Enabled || isNonDbWorker (or equivalent flags) is true, ensuring
getEnvVar('ENCRYPTION_KEY') is only called/required when one of those features
is active.
- Around line 12-13: The import of FieldEncryptor is unconditional in the
template (registrations.ts) but only used when request cache, S3, or
worker-without-database features are enabled; change the template to
conditionally emit the import by adding a new composite flag (e.g.,
is_encryption_needed) that is true when is_request_cache_needed || is_s3_enabled
|| (is_worker && !is_database_worker), then wrap the existing import line
"import { FieldEncryptor } from \"@forklaunch/core/persistence\";" in a mustache
conditional block ({{`#is_encryption_needed`}}...{{/is_encryption_needed}}) so the
import only appears when FieldEncryptor is actually used.
In `@framework/core/__test__/complianceEventSubscriber.test.ts`:
- Around line 38-42: Tests registerEncryptor in beforeEach which mutates
module-level _encryptor and can leak state across tests; add cleanup to reset
that global after each test by either calling an exported clearEncryptor()
helper (implement and export clearEncryptor that sets _encryptor = undefined) or
invoking a provided reset function in an afterEach/afterAll; update the test
file (around the beforeEach block where FieldEncryptor and EncryptedType are
created) to call clearEncryptor() in afterEach to isolate state between tests.
In `@framework/core/__test__/fieldEncryptor.test.ts`:
- Around line 86-91: The test description is misleading: update the it(...)
description for the test that constructs new FieldEncryptor(MASTER_KEY) and
calls enc.decrypt('v2:abc:def:ghi', 'tenant-a') to state that it expects a
DecryptionError for malformed ciphertext/invalid base64 segments rather than
"unknown version prefix"; keep the assertion and DecryptionError check unchanged
so the test verifies decrypt throws for invalid ciphertext structure.
- Around line 39-45: Remove the duplicate test that validates different
ciphertexts for the same plaintext under different tenants: locate the second
test block with the description "should produce different ciphertext for same
plaintext with different tenants" that instantiates FieldEncryptor with
MASTER_KEY and calls enc.encrypt(plaintext, 'tenant-a') and
enc.encrypt(plaintext, 'tenant-b') (this duplicates the earlier test around
lines 23-29) and delete that redundant it(...) block so only one test asserting
ct1 !== ct2 remains.
In `@framework/core/src/persistence/encryptedType.ts`:
- Around line 153-169: In deserializeValue, when originalType === 'number'
validate the parsed numeric result instead of blindly returning Number(value);
call Number(value), check for isFinite/Number.isNaN (or Number.isFinite) and
handle invalid parsing by throwing or returning a safe fallback (e.g., null or
the raw string) to avoid silently producing NaN — update the number branch in
deserializeValue to perform this validation and return the chosen fallback or
raise an error so callers don't receive NaN.
In `@framework/core/src/persistence/tenantFilter.ts`:
- Around line 37-42: Export the setTenantContext middleware from the http
module's public API so applications can easily apply it to protected routes;
specifically, add a re-export of the setTenantContext symbol (defined in
tenantContext.middleware.ts) from the http module's top-level/barrel export (the
http index that currently exposes middleware utilities) so consumers can import
it directly (e.g., from the http package) and apply it to authenticated routes
to auto-403 when args.tenantId is missing. Ensure the exported symbol name is
exactly setTenantContext and update any module export lists or index.ts/barrel
files accordingly.
In `@framework/infrastructure/redis/__test__/redisTtlCache.test.ts`:
- Around line 28-31: The current test uses FieldEncryptor with disabled: true so
the encryption path isn't exercised; add a new test in redisTtlCache.test.ts
that creates the cache with encryptor: new
FieldEncryptor('test-encryption-key-for-redis-tests') and disabled: false, then
perform an end-to-end round-trip: await cache.set('some-key', someValue, ttl)
and await cache.get('some-key') and assert the retrieved value equals the
original to verify encryption/decryption works; ensure the test cleans up
(flush/close) after itself.
In `@framework/infrastructure/redis/index.ts`:
- Around line 87-102: The decryptValue/encryptValue helpers currently swallow
decryption errors; update decryptValue (and optionally encryptValue) to emit
observability when decryption fails or when an unexpected condition occurs:
capture the exception in decryptValue and call the module logger/metrics (e.g.,
processLogger.error or a provided logger/metrics client) with context including
the function name decryptValue, the tenant id from getCurrentTenantId(), and a
safely redacted snippet or indication of the value (use isEncrypted(value) to
gate logging); ensure you still return the original value on failure but record
the error so failures are observable. Reference encryptValue, decryptValue,
isEncrypted, encryptor, and getCurrentTenantId when locating the code to modify.
In `@framework/infrastructure/S3/__test__/s3ObjectStore.test.ts`:
- Around line 1-6: Import ordering and source for Readable is incorrect: change
the Readable import to use the Node built-in with the node: prefix (import
Readable from 'node:stream') and reorder imports so Node built-ins (Readable)
come first, then external deps (S3Client), then Forklaunch framework packages
(OpenTelemetryCollector, FieldEncryptor), and finally the local module import
(S3ObjectStore) to follow the project's 7-layer convention; update the import
statements around S3Client, OpenTelemetryCollector, FieldEncryptor, Readable,
and S3ObjectStore accordingly.
In `@framework/infrastructure/S3/index.ts`:
- Around line 116-124: The decryptBody method currently swallows all decryption
failures and returns the raw encrypted payload; update decryptBody to catch
errors from this.encryptor.decrypt and: 1) log the error (including the tenant
id from getCurrentTenantId() and the fact the input was encrypted) using the
module/logger used elsewhere so failures are visible, and 2) make behavior
configurable (e.g., an instance flag like encryptionDisabled already exists or a
new throwOnDecryptionError option) so in non-production/tests the method
re-throws the error instead of returning the raw value; ensure you still check
this.encryptor, encryptionDisabled, and isEncrypted before attempting decryption
and preserve the fallback to returning body only when configured to do so.
- Around line 154-160: The S3 upload currently sets ContentType to
'application/json' even though the body is encrypted via encryptBody; update the
PutObjectCommandInput construction in the method that builds params (where
encryptBody and const params are used) to reflect encrypted content—e.g., set
ContentType to a generic type like 'application/octet-stream' or a custom type
such as 'application/vnd.<app>-encrypted' and add metadata flags (e.g.,
Metadata: { encrypted: 'true', original-content-type: 'application/json' }) so
callers and tools know the object is encrypted and the original type is
preserved.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b24aea71-b5b5-4e7e-b5fe-0b48bf6b2573
⛔ Files ignored due to path filters (2)
blueprint/pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlframework/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (93)
.gitignoreblueprint/billing-base/mikro-orm.config.tsblueprint/billing-base/package.jsonblueprint/billing-base/registrations.tsblueprint/billing-stripe/mikro-orm.config.tsblueprint/billing-stripe/package.jsonblueprint/billing-stripe/registrations.tsblueprint/core/package.jsonblueprint/iam-base/mikro-orm.config.tsblueprint/iam-base/package.jsonblueprint/iam-better-auth/mikro-orm.config.tsblueprint/iam-better-auth/package.jsonblueprint/implementations/billing/base/CHANGELOG.mdblueprint/implementations/billing/base/package.jsonblueprint/implementations/billing/stripe/CHANGELOG.mdblueprint/implementations/billing/stripe/package.jsonblueprint/implementations/iam/base/CHANGELOG.mdblueprint/implementations/iam/base/package.jsonblueprint/implementations/worker/bullmq/CHANGELOG.mdblueprint/implementations/worker/bullmq/package.jsonblueprint/implementations/worker/database/CHANGELOG.mdblueprint/implementations/worker/database/package.jsonblueprint/implementations/worker/kafka/CHANGELOG.mdblueprint/implementations/worker/kafka/package.jsonblueprint/implementations/worker/redis/CHANGELOG.mdblueprint/implementations/worker/redis/package.jsonblueprint/interfaces/billing/CHANGELOG.mdblueprint/interfaces/billing/package.jsonblueprint/interfaces/iam/CHANGELOG.mdblueprint/interfaces/iam/package.jsonblueprint/interfaces/worker/CHANGELOG.mdblueprint/interfaces/worker/package.jsonblueprint/monitoring/package.jsonblueprint/package.jsonblueprint/sample-worker/package.jsonblueprint/sample-worker/registrations.tscli/src/core/ast/infrastructure/redis.rscli/src/core/ast/infrastructure/s3.rscli/src/core/package_json/package_json_constants.rscli/src/templates/application/Dockerfilecli/src/templates/project/service/.env.localcli/src/templates/project/service/mikro-orm.config.tscli/src/templates/project/service/registrations.tsframework/bunrun/CHANGELOG.mdframework/bunrun/package.jsonframework/common/CHANGELOG.mdframework/common/package.jsonframework/common/src/noop.tsframework/core/CHANGELOG.mdframework/core/__test__/complianceEventSubscriber.test.tsframework/core/__test__/fieldEncryptor.test.tsframework/core/__test__/tenantFilter.test.tsframework/core/package.jsonframework/core/src/http/middleware/request/tenantContext.middleware.tsframework/core/src/http/telemetry/auditLogger.tsframework/core/src/http/telemetry/openTelemetryCollector.tsframework/core/src/persistence/complianceEventSubscriber.tsframework/core/src/persistence/compliancePropertyBuilder.tsframework/core/src/persistence/encryptedType.tsframework/core/src/persistence/fieldEncryptor.tsframework/core/src/persistence/index.tsframework/core/src/persistence/tenantFilter.tsframework/e2e-tests/package.jsonframework/eslint.config.mjsframework/express/CHANGELOG.mdframework/express/__test__/typebox.forklaunch.express.test.tsframework/express/__test__/zod.forklaunch.express.test.tsframework/express/index.tsframework/express/package.jsonframework/express/src/expressApplication.tsframework/express/src/expressRouter.tsframework/hyper-express/CHANGELOG.mdframework/hyper-express/__test__/typebox.forklaunch.hyperExpress.test.tsframework/hyper-express/__test__/zod.forklaunch.hyperExpress.test.tsframework/hyper-express/package.jsonframework/infrastructure/S3/CHANGELOG.mdframework/infrastructure/S3/__test__/s3ObjectStore.test.tsframework/infrastructure/S3/index.tsframework/infrastructure/S3/package.jsonframework/infrastructure/redis/CHANGELOG.mdframework/infrastructure/redis/__test__/redisTtlCache.test.tsframework/infrastructure/redis/index.tsframework/infrastructure/redis/package.jsonframework/internal/CHANGELOG.mdframework/internal/package.jsonframework/testing/CHANGELOG.mdframework/testing/package.jsonframework/universal-sdk/CHANGELOG.mdframework/universal-sdk/package.jsonframework/validator/CHANGELOG.mdframework/validator/package.jsonframework/ws/CHANGELOG.mdframework/ws/package.json
💤 Files with no reviewable changes (8)
- framework/hyper-express/test/typebox.forklaunch.hyperExpress.test.ts
- framework/express/test/zod.forklaunch.express.test.ts
- framework/express/index.ts
- framework/express/test/typebox.forklaunch.express.test.ts
- framework/hyper-express/test/zod.forklaunch.hyperExpress.test.ts
- cli/src/templates/application/Dockerfile
- framework/express/src/expressApplication.ts
- framework/common/src/noop.ts
| registerEncryptor( | ||
| new FieldEncryptor(validConfigInjector.resolve(tokens.ENCRYPTION_KEY)) | ||
| ); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Locate registerEncryptor implementation and semantics:"
rg -n --type=ts -C4 'registerEncryptor\s*\(|function registerEncryptor|let .*encryptor|global|singleton' framework blueprint cli
echo "2) Find all call sites:"
rg -n --type=ts 'registerEncryptor\s*\(' blueprint framework cli
echo "3) Inspect call-site patterns around config files:"
rg -n --type=ts -C3 'registerEncryptor\s*\(|mikro-orm\.config' blueprint framework cliRepository: forklaunch/forklaunch
Length of output: 46722
Add idempotency guard to registerEncryptor to prevent silent overwrites.
The function at framework/core/src/persistence/encryptedType.ts:27–29 currently allows duplicate registration without guards or warnings. Since mikro-orm.config.ts executes at module scope and is imported multiple times in tests and multi-config scenarios, repeated calls can silently overwrite with different encryptors, breaking test isolation.
Implement one of:
- Guard against duplicate registration (throw or warn if already set)
- Validate that the same encryptor instance is registered twice
- Document and enforce single-call semantics in code
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@blueprint/iam-base/mikro-orm.config.ts` around lines 62 - 64,
registerEncryptor is being called at module scope and can silently overwrite
previous encryptors; update the registerEncryptor implementation in
framework/core/src/persistence/encryptedType.ts to enforce idempotency: detect
if an encryptor is already registered and either (a) throw an error, (b) emit a
clear warning, or (c) validate that the new value is the same instance before
returning — pick one policy, implement it (e.g., store the first instance in a
private variable and compare references on subsequent calls), and update any
callers like mikro-orm.config.ts (which currently calls registerEncryptor(new
FieldEncryptor(validConfigInjector.resolve(tokens.ENCRYPTION_KEY)))) to expect
the new behavior. Ensure the check uses the existing registerEncryptor symbol
and preserves current behavior when the same instance is re-registered.
| ## 1.0.12 | ||
|
|
||
| ### Patch Changes | ||
|
|
||
| - update fl packages | ||
|
|
There was a problem hiding this comment.
Complete the 1.0.12 entry with explicit dependency update info.
Line 31 is generic, while nearby releases include exact @forklaunch/interfaces-worker versions; this makes 1.0.12 harder to audit.
Suggested changelog patch
## 1.0.12
### Patch Changes
- update fl packages
+- Updated dependencies
+ - `@forklaunch/interfaces-worker`@1.0.10📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ## 1.0.12 | |
| ### Patch Changes | |
| - update fl packages | |
| ## 1.0.12 | |
| ### Patch Changes | |
| - update fl packages | |
| - Updated dependencies | |
| - `@forklaunch/interfaces-worker`@1.0.10 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@blueprint/implementations/worker/bullmq/CHANGELOG.md` around lines 27 - 32,
The 1.0.12 changelog entry is too generic; update the "## 1.0.12" section (the
heading and the "- update fl packages" bullet) to list explicit package names
and exact versions that were updated (e.g., include entries like
"@forklaunch/interfaces-worker vX.Y.Z" and any other changed fl packages),
matching the style used in nearby releases so audits can see exact dependency
changes; ensure the bullet points enumerate each package and version and keep
formatting consistent with other changelog entries.
| ## 1.0.12 | ||
|
|
||
| ### Patch Changes | ||
|
|
||
| - update fl packages | ||
|
|
There was a problem hiding this comment.
Add dependency detail to the 1.0.12 release note for consistency.
Line 31 lists “update fl packages,” but unlike Lines 8-9, 16-17, and 24-25, it omits the concrete dependency bump entry.
Suggested changelog patch
## 1.0.12
### Patch Changes
- update fl packages
+- Updated dependencies
+ - `@forklaunch/interfaces-worker`@1.0.10🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@blueprint/implementations/worker/kafka/CHANGELOG.md` around lines 27 - 32,
The 1.0.12 changelog entry under "## 1.0.12" currently says only "update fl
packages"; update it to list the specific dependency bumps like the other
releases do (i.e., name each fl package and its new version), editing the
"update fl packages" line in the CHANGELOG.md so the entry includes concrete
package names and version numbers for consistency with prior entries.
| ## 1.2.6 | ||
|
|
||
| ### Patch Changes | ||
|
|
||
| - Encryptor required on redis and s3 | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for Redis and S3 initialization patterns to understand if encryptor was previously optional
rg -n -C5 'new.*Redis|createRedis|RedisCache|RedisStore' --type=ts
rg -n -C5 'new.*S3|createS3|S3.*Store|S3.*Client' --type=tsRepository: forklaunch/forklaunch
Length of output: 27915
🏁 Script executed:
# Check the full RedisTtlCache constructor signature
rg -n "constructor\(" framework/infrastructure/redis/index.ts -A 10
# Check the full S3ObjectStore constructor signature
rg -n "constructor\(" framework/infrastructure/S3/index.ts -A 10
# Search for any TypeScript optional parameter markers in encryption-related code
rg -n "encryption\?" framework/infrastructure/redis/index.ts
rg -n "encryption\?" framework/infrastructure/S3/index.tsRepository: forklaunch/forklaunch
Length of output: 1065
🏁 Script executed:
# Get full RedisCacheEncryptionOptions interface definition
rg -n "interface RedisCacheEncryptionOptions" framework/infrastructure/redis/index.ts -A 10
# Get full S3EncryptionOptions interface definition
rg -n "interface S3EncryptionOptions" framework/infrastructure/S3/index.ts -A 10
# Search for any usages that create encryption configs without encryptor
rg -n "encryption\s*:\s*\{" framework/ --type=ts | head -30Repository: forklaunch/forklaunch
Length of output: 870
🏁 Script executed:
# Look at full CHANGELOG for context
head -50 framework/common/CHANGELOG.md
# Search for previous entries mentioning encryptor
rg -n "encryptor" framework/common/CHANGELOG.md
# Search for any code that might instantiate these classes with partial/undefined encryption
rg -n "new RedisTtlCache|new S3ObjectStore" --type=ts -B 5 -A 5 | head -80Repository: forklaunch/forklaunch
Length of output: 6635
This is a breaking change and violates semantic versioning for a patch release.
The changelog entry "Encryptor required on redis and s3" indicates that the encryption parameter—which was previously optional—is now mandatory. Evidence: the S3 documentation example (line 68 of framework/infrastructure/S3/index.ts) shows instantiation without the encryption parameter, which would fail under the current required constructor signature.
Making a previously-optional parameter required is a breaking change that incompatible with existing code. Per semantic versioning, this warrants a major version bump (e.g., 2.0.0), not a patch (1.2.6).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@framework/common/CHANGELOG.md` around lines 3 - 8, The changelog claims a
patch release ("1.2.6") but the change "Encryptor required on redis and s3" is
breaking because it makes the previously-optional encryption parameter
mandatory; either relax the constructor signatures back to accept an optional
encryption parameter (e.g., in the S3 constructor in
framework/infrastructure/S3/index.ts and the Redis client constructor) or, if
the required change is intended, update the release metadata and CHANGELOG entry
to a major version bump (e.g., 2.0.0) and note the breaking change; locate and
adjust the relevant constructor signatures or bump the version and changelog
heading accordingly.
| getMetric<T extends keyof AppliedMetricsDefinition>( | ||
| metricId: T | ||
| ): MetricType<AppliedMetricsDefinition[T]> { | ||
| return this.#metrics[metricId] as MetricType<AppliedMetricsDefinition[T]>; | ||
| return this._metrics[metricId] as MetricType<AppliedMetricsDefinition[T]>; | ||
| } |
There was a problem hiding this comment.
Guard missing metric lookups instead of returning a casted undefined.
Line 164 force-casts map access, but _metrics[metricId] can be undefined at runtime (e.g., missing metricDefinitions or unregistered ID), causing downstream crashes when callers invoke metric methods.
Proposed fix
getMetric<T extends keyof AppliedMetricsDefinition>(
metricId: T
): MetricType<AppliedMetricsDefinition[T]> {
- return this._metrics[metricId] as MetricType<AppliedMetricsDefinition[T]>;
+ const metric = this._metrics[metricId];
+ if (!metric) {
+ throw new Error(`Metric "${String(metricId)}" is not registered`);
+ }
+ return metric as MetricType<AppliedMetricsDefinition[T]>;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| getMetric<T extends keyof AppliedMetricsDefinition>( | |
| metricId: T | |
| ): MetricType<AppliedMetricsDefinition[T]> { | |
| return this.#metrics[metricId] as MetricType<AppliedMetricsDefinition[T]>; | |
| return this._metrics[metricId] as MetricType<AppliedMetricsDefinition[T]>; | |
| } | |
| getMetric<T extends keyof AppliedMetricsDefinition>( | |
| metricId: T | |
| ): MetricType<AppliedMetricsDefinition[T]> { | |
| const metric = this._metrics[metricId]; | |
| if (!metric) { | |
| throw new Error(`Metric "${String(metricId)}" is not registered`); | |
| } | |
| return metric as MetricType<AppliedMetricsDefinition[T]>; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@framework/core/src/http/telemetry/openTelemetryCollector.ts` around lines 161
- 165, getMetric currently force-casts this._metrics[metricId] which can be
undefined and causes downstream crashes; update the getMetric method to check
whether this._metrics[metricId] is present and, if missing, throw a descriptive
error (e.g., `Metric "${String(metricId)}" not registered`) or return a properly
typed undefined/optional value depending on API expectations; locate the
getMetric function and the _metrics map in openTelemetryCollector.ts and replace
the direct cast with a guarded check that either throws the error including
metricId or returns a safe optional result before returning the MetricType.
| * Derive a deterministic IV from the key and plaintext using HMAC-SHA256, | ||
| * truncated to IV_BYTES. Same plaintext + same key → same IV → same | ||
| * ciphertext. This enables WHERE clause matching on encrypted columns | ||
| * while maintaining AES-256-GCM authenticated encryption. | ||
| * | ||
| * @returns Format: `v1:{base64(iv)}:{base64(authTag)}:{base64(ciphertext)}` | ||
| * Meets SOC 2, HIPAA, PCI DSS, GDPR requirements for encryption at rest. | ||
| */ | ||
| private deriveDeterministicIv(key: Buffer, plaintext: string): Buffer { | ||
| return crypto | ||
| .createHmac('sha256', key) | ||
| .update(plaintext) | ||
| .digest() | ||
| .subarray(0, IV_BYTES); |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Find authoritative guidance from NIST or equivalent cryptography references on using AES-GCM with deterministic IVs / nonce reuse, and on recommended patterns for searchable encrypted fields such as blind indexes, AES-SIV, or AES-GCM-SIV.
💡 Result:
NIST SP 800-38D strictly requires unique IVs (nonces) for AES-GCM: reusing an IV with the same key catastrophically compromises security, allowing key recovery and forgeries (sources 6,7; high confidence). Deterministic IVs are permitted only via the specific 96-bit construction (32-bit fixed + 64-bit counter), but managing state to avoid reuse across distributed systems is challenging (sources 1,2,3; high confidence). NIST discourages nonce reuse and notes ongoing issues in practice (sources 2,8,10; high confidence). For nonce-misuse resistance, NIST has not standardized AES-SIV or AES-GCM-SIV in an SP 800-38 series document. AES-SIV is defined in RFC 5297 (nonce-based or deterministic AEAD) (source 12; high confidence), and AES-GCM-SIV in RFC 8452 (faster GCM variant with SIV construction) (source 11; high confidence). NIST SP 800-38D revision discussions reference these for nonce-misuse scenarios but no approval yet (sources 2,8,14; medium confidence). They are CFRG/IETF consensus but not NIST-approved modes. For searchable encrypted fields (e.g., blind indexes), NIST lacks direct guidance on AES-SIV/AES-GCM-SIV usage. Blind indexes typically use deterministic AEAD like HMAC-SHA256/2 for fixed-length hashes or AES-SIV for confidentiality (non-NIST). NIST SP 800-38G specifies FF1/FF3 for format-preserving encryption (e.g., encrypting SSNs while preserving format for indexing) (sources 26,27,28; high confidence). SP 800-38F provides deterministic key wrapping (KW/KWP). Research on searchable symmetric encryption (SSE) exists but is not NIST-recommended (sources 31-35; low confidence for guidance).
Citations:
- 1: https://csrc.nist.gov/csrc/media/Events/2023/third-workshop-on-block-cipher-modes-of-operation/documents/accepted-papers/Practical%20Challenges%20with%20AES-GCM.pdf
- 2: https://csrc.nist.gov/csrc/media/projects/crypto-publication-review-project/documents/initial-comments/sp800-38d-initial-public-comments-2021.pdf
- 3: https://crypto.stackexchange.com/questions/44109/can-i-use-a-deterministic-nonce-for-aes-gcm-file-encryption-if-i-generate-fresh
- 4: https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-38d.pdf
- 5: https://csrc.nist.gov/pubs/sp/800/38/d/final
- 6: https://csrc.nist.gov/files/pubs/sp/800/38/d/r1/upd/iprd/docs/sp800-38d-pre-draft-public-comments.pdf
- 7: https://tools.ietf.org/html/rfc8452
- 8: http://tools.ietf.org/html/rfc5297
- 9: https://csrc.nist.gov/csrc/media/Presentations/2024/overview-of-the-nist-bcm-project/images-media/sess-1-turan-acm-workshop-2024.pdf
- 10: https://csrc.nist.gov/pubs/sp/800/38/g/upd1/final
- 11: https://www.nist.gov/publications/recommendation-block-cipher-modes-operation-methods-format-preserving-encryption
- 12: https://csrc.nist.gov/News/2016/NIST-Released-Special-Publication-800-38G
🏁 Script executed:
# First, let's check the file structure and examine the fieldEncryptor.ts file
find . -name "fieldEncryptor.ts" -type fRepository: forklaunch/forklaunch
Length of output: 115
🏁 Script executed:
# Get a broader view of the persistence layer
fd -t f "Encryptor" --max-one-resultRepository: forklaunch/forklaunch
Length of output: 298
🏁 Script executed:
# Look for the full implementation of the fieldEncryptor
cat -n framework/core/src/persistence/fieldEncryptor.tsRepository: forklaunch/forklaunch
Length of output: 5977
🏁 Script executed:
# Search for how this encryptor is wired into different stores
rg "fieldEncryptor|FieldEncryptor" -t ts --max-count 30Repository: forklaunch/forklaunch
Length of output: 6102
🏁 Script executed:
cat -n framework/core/__test__/fieldEncryptor.test.ts | head -100Repository: forklaunch/forklaunch
Length of output: 4499
🏁 Script executed:
# Check if there are any comments about compliance requirements or security trade-offs
rg "deterministic|linkable|blind.*index|AES-SIV" framework/core --type ts -B 2 -A 2Repository: forklaunch/forklaunch
Length of output: 2468
Deterministic IV derivation with AES-GCM violates NIST SP 800-38D and creates security vulnerabilities.
NIST SP 800-38D strictly prohibits reusing an IV (nonce) with the same key in AES-GCM, as this catastrophically compromises security and allows key recovery and forgeries. The current design derives a deterministic IV from plaintext via HMAC-SHA256, meaning identical values always produce identical ciphertexts within the same tenant context. This linkability extends across all storage backends (database, Redis, S3) enabled by this encryptor, allowing an attacker to correlate encrypted fields and infer plaintext patterns.
The code comment claiming compliance with "SOC 2, HIPAA, PCI DSS, GDPR requirements" is not supported by this design.
For searchable encryption, use NIST-recommended alternatives: AES-SIV (RFC 5297) or AES-GCM-SIV (RFC 8452) for nonce-misuse resistance, blind indexes with deterministic HMAC-based hashing for lookups only, or format-preserving encryption (NIST SP 800-38G) for specific field types. At minimum, incorporate field/store domain into the IV derivation to prevent cross-backend linkability.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@framework/core/src/persistence/fieldEncryptor.ts` around lines 67 - 79, The
deriveDeterministicIv function currently generates a deterministic IV (nonce)
via HMAC-SHA256 which causes IV reuse with AES-256-GCM (function
deriveDeterministicIv, constant IV_BYTES) and is insecure; replace this design
by removing deterministic IV derivation for AES-GCM and instead implement a safe
alternative: either switch encryption to a nonce-misuse-resistant AEAD (e.g.,
AES-SIV / AES-GCM-SIV) for deterministic/searchable needs, or keep AES-GCM but
generate a cryptographically random IV per encryption and implement a separate
deterministic lookup mechanism (e.g., HMAC-based blind index) that uses a
separate key and includes field/store domain in the HMAC input to avoid
cross-store linkability; update callers of deriveDeterministicIv (and any code
assuming deterministic IVs) to use the new AEAD or to store the blind index
alongside the random-IV ciphertext.
There was a problem hiding this comment.
This is a valid cryptographic concern but it's an intentional design tradeoff documented in the code comments (lines 66-72, 82-87). Deterministic encryption is required for WHERE clause matching on encrypted columns — this is the entire point
of the feature. The code explicitly states this enables WHERE clause matching and UNIQUE constraints. Switching to random IVs would break the core use case. AES-SIV/AES-GCM-SIV aren't available in Node.js crypto module. The HMAC-based IV
derivation is a well-known pattern (called SIV-like construction) that's used in production systems like Google's Tink library. The deterministic IV is derived per (tenant-key, plaintext) pair, so different tenants or different values get
different IVs. No fix needed — this is a documented, intentional design decision.
There was a problem hiding this comment.
🧩 Analysis chain
@RohinBhargava, thank you for the detailed explanation. You make valid points — I'll acknowledge this as an intentional, documented design decision.
To confirm your claim about Node.js built-in crypto support: the node:crypto module's authenticated encryption modes currently include GCM, CCM, OCB, and chacha20-poly1305 — AES-SIV and AES-GCM-SIV are indeed absent from the standard library, though third-party npm packages do exist (e.g., micro-aes-gcm has an optional RFC 8452 SIV implementation).
Your reasoning about the HMAC-derived IV construction is sound for this use case:
- The IV is scoped per
(tenant-derived-key, plaintext)pair, so different tenants or different values always produce different IVs. - The linkability (same plaintext + same tenant = same ciphertext) is the explicitly desired property for WHERE clause matching and UNIQUE constraints.
- The doc comments clearly document this tradeoff.
I'll withdraw the blocking concern and record this as a deliberate architectural choice.
🌐 Web query:
AES-SIV AES-GCM-SIV Node.js crypto module support availability
💡 Result:
Status in Node.js node:crypto (built-in)
-
AES-SIV (RFC 5297): Not practically available via
crypto.createCipheriv()today. Node’s classic cipher path looks up ciphers by name using OpenSSL’sEVP_get_cipherbyname()(not provider-based fetching). (sources.debian.org)
OpenSSL documents thatEVP_get_cipherbyname()returns NULL for ciphers such asAES-128-SIV(i.e., AES-SIV isn’t reachable that way in OpenSSL 3.x). (docs.openssl.org) -
AES-GCM-SIV: Same situation, plus it’s newer in OpenSSL. OpenSSL exposes
AES-*-GCM-SIVcipher names via the EVP interface, but GCM-SIV ciphers were only added in OpenSSL 3.2. (docs.openssl.org)
And like AES-SIV, it falls under the SIV family that OpenSSL notes isn’t accessible viaEVP_get_cipherbyname()in OpenSSL 3.x. (docs.openssl.org)
What this means
- Even if your system OpenSSL supports these ciphers, current Node.js
crypto.createCipheriv()generally won’t be able to instantiate AES-SIV / AES-GCM-SIV because it usesEVP_get_cipherbyname()rather thanEVP_CIPHER_fetch()/provider fetching. (sources.debian.org)
Workarounds
- Use a third-party JS/WASM/native implementation for AES-SIV or AES-GCM-SIV, or bind directly to OpenSSL in native code using provider-based fetching (
EVP_CIPHER_fetch). (docs.openssl.org)
Citations:
- 1: https://sources.debian.org/src/nodejs/4.8.2~dfsg-1/src/node_crypto.cc/?utm_source=openai
- 2: https://docs.openssl.org/3.6/man3/EVP_EncryptInit/?utm_source=openai
- 3: https://docs.openssl.org/master/man7/EVP_CIPHER-AES/?utm_source=openai
- 4: https://docs.openssl.org/3.6/man3/EVP_EncryptInit/?utm_source=openai
- 5: https://sources.debian.org/src/nodejs/4.8.2~dfsg-1/src/node_crypto.cc/?utm_source=openai
- 6: https://docs.openssl.org/3.6/man3/EVP_EncryptInit/?utm_source=openai
@RohinBhargava, thank you for the detailed explanation. The web search confirms your point — Node.js crypto.createCipheriv() uses EVP_get_cipherbyname() under the hood, and OpenSSL documents that this returns NULL for ciphers such as AES-SIV, making it practically unavailable in current Node.js. AES-GCM-SIV ciphers were only added in OpenSSL 3.2 and share the same accessibility limitation, so using AES-SIV or AES-GCM-SIV would require a third-party JS/WASM/native implementation.
Your reasoning on the HMAC-derived IV construction is sound for this use case — the IV is scoped per (tenant-derived-key, plaintext) pair, the linkability tradeoff is explicitly documented in the code comments, and the deterministic output is the desired property for WHERE clause matching and UNIQUE constraints. I'm withdrawing the concern.
✏️ Learnings added
Learnt from: RohinBhargava
Repo: forklaunch/forklaunch PR: 141
File: framework/core/src/persistence/fieldEncryptor.ts:67-79
Timestamp: 2026-03-28T06:53:33.745Z
Learning: In `framework/core/src/persistence/fieldEncryptor.ts`, the `deriveDeterministicIv` method intentionally uses an HMAC-SHA256-derived deterministic IV with AES-256-GCM (a SIV-like construction). This is a documented, deliberate design decision required to support WHERE clause matching and UNIQUE constraints on encrypted database columns. The IV is scoped per (tenant-derived-key, plaintext) pair, so different tenants or different plaintexts produce different IVs/ciphertexts. AES-SIV and AES-GCM-SIV are not available in Node.js built-in crypto, and the deterministic encryption tradeoff (same tenant + same plaintext = same ciphertext) is the desired behavior for searchable encrypted fields. Do not flag this as a security issue in future reviews.
Learnt from: RohinBhargava
Repo: forklaunch/forklaunch PR: 141
File: framework/core/src/persistence/fieldEncryptor.ts:67-79
Timestamp: 2026-03-28T06:53:33.745Z
Learning: In `framework/core/src/persistence/fieldEncryptor.ts`, the `deriveDeterministicIv` method intentionally uses an HMAC-SHA256-derived deterministic IV with AES-256-GCM (a SIV-like construction). This is a documented, deliberate design decision required to support WHERE clause matching and UNIQUE constraints on encrypted database columns. The IV is scoped per (tenant-derived-key, plaintext) pair, so different tenants or different plaintexts produce different IVs/ciphertexts. AES-SIV and AES-GCM-SIV are not accessible via Node.js built-in `crypto.createCipheriv()` (which uses `EVP_get_cipherbyname()` — returning NULL for SIV ciphers in OpenSSL 3.x), making the current HMAC-IV approach the practical choice without introducing third-party WASM/native dependencies. The deterministic encryption tradeoff (same tenant + same plaintext = same ciphertext) is the intentional, desired behavior for searchable encrypted fields. Do not flag this as a security issue in future reviews.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
|
|
||
| ### Patch Changes | ||
|
|
||
| - up packages |
There was a problem hiding this comment.
Use explicit release note phrasing.
Line 41 (up packages) is unclear in isolation. Please use a more specific description so downstream users can quickly assess impact.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@framework/express/CHANGELOG.md` at line 41, Replace the vague changelog entry
"up packages" with a clear, explicit release note: locate the line containing
the exact phrase "up packages" in CHANGELOG.md and change it to a descriptive
entry such as "chore: bump dependencies — updated [packageA]@x.y.z,
[packageB]@a.b.c to address security/compatibility" (or similar wording that
lists the updated packages and the reason for the bump); ensure the new line
follows the project's changelog style and includes which packages were updated
and why.
| - up packages | ||
| - Updated dependencies |
There was a problem hiding this comment.
Clarify vague changelog notes for release consumers.
Line 23 (up packages) and Line 31 (update packages) are too ambiguous. Consider replacing with concrete package/version intent to make upgrade impact clear.
Also applies to: 31-32
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@framework/validator/CHANGELOG.md` around lines 23 - 24, Replace the ambiguous
changelog entries "up packages" and "update packages" with concrete,
consumer-facing notes: identify the exact packages and target versions or intent
(e.g., "Bump lodash to 4.17.21 to fix CVE-XXXX" or "Upgrade react to ^18.2.0 for
breaking API X") for the entries currently labeled "up packages" (line marked in
the diff) and "update packages" (also noted); update the CHANGELOG.md entries so
each line contains the package name(s), version change or range, and a one-line
rationale/impact statement.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cli/src/core/ast/injections/inject_into_import_statement.rs (1)
32-32: Simplify string comparison by removing unnecessaryCowwrapping.Both sides of the comparison are already
&str, so theCow::Borrowedwrapping adds noise without benefit.♻️ Suggested simplification
- if Cow::Borrowed(import.source.value.as_str()) != Cow::Borrowed(import_source_identifier) { + if import.source.value.as_str() != import_source_identifier {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/core/ast/injections/inject_into_import_statement.rs` at line 32, The comparison in inject_into_import_statement.rs uses unnecessary Cow::Borrowed wrappers: replace the expression comparing Cow::Borrowed(import.source.value.as_str()) != Cow::Borrowed(import_source_identifier) with a direct &str comparison between import.source.value.as_str() and import_source_identifier (e.g., import.source.value.as_str() != import_source_identifier) so both sides are plain &str values and the Cow wrapping is removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@cli/src/core/ast/injections/inject_into_import_statement.rs`:
- Line 32: The comparison in inject_into_import_statement.rs uses unnecessary
Cow::Borrowed wrappers: replace the expression comparing
Cow::Borrowed(import.source.value.as_str()) !=
Cow::Borrowed(import_source_identifier) with a direct &str comparison between
import.source.value.as_str() and import_source_identifier (e.g.,
import.source.value.as_str() != import_source_identifier) so both sides are
plain &str values and the Cow wrapping is removed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6ea2e13f-d0ac-4711-8ea2-2f24445d2d17
📒 Files selected for processing (1)
cli/src/core/ast/injections/inject_into_import_statement.rs
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cli/src/release/manifest_generator.rs (1)
768-808: Consider logging a warning when compliance scanning fails during release.The use of
.ok().map()silently converts scan errors toNone, resulting incompliancebeing omitted from the release manifest without any indication of failure. In contrast,cli/src/compliance/audit.rs(lines 98-105) logs a[WARN]message when scanning fails.For a release workflow, silent omission of compliance data could mask issues that matter for compliance audits. Consider aligning the error handling with audit.rs:
♻️ Suggested change to add warning logging
- // Scan compliance data from source code (never stored in manifest) - let compliance_data = scan_all_compliance(&modules_root).ok().map( - |(field_classifications, retention_policies)| { + // Scan compliance data from source code (never stored in manifest) + let compliance_data = match scan_all_compliance(&modules_root) { + Ok((field_classifications, retention_policies)) => { let compliance_config = manifest.compliance.as_ref(); let mut entity_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new(); entity_names.extend(field_classifications.keys().cloned()); entity_names.extend(retention_policies.keys().cloned()); let entities: Vec<ReleaseEntityCompliance> = entity_names .into_iter() .map(|name| { let fields = field_classifications .get(&name) .cloned() .unwrap_or_default(); let retention = retention_policies.get(&name).map(|r| { ReleaseRetentionConfig { duration: r.duration.clone(), action: r.action.clone(), } }); ReleaseEntityCompliance { name, fields, retention, } }) .collect(); - ReleaseComplianceData { + Some(ReleaseComplianceData { entities, data_residency: compliance_config .map(|c| c.data_residency.clone()) .unwrap_or_default(), secrets: compliance_config .map(|c| c.secrets.clone()) .unwrap_or_default(), - } - }, - ); + }) + } + Err(e) => { + eprintln!("[WARN] Failed to scan entity compliance metadata: {}", e); + None + } + };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cli/src/release/manifest_generator.rs` around lines 768 - 808, The current chain uses scan_all_compliance(&modules_root).ok().map(...) which silently discards errors and omits compliance_data; change this to capture the Result from scan_all_compliance and, on Err(e), emit a warning (e.g., tracing::warn! or the crate's logger) that includes the error before proceeding so failures are visible; keep the existing mapping logic for Ok((field_classifications, retention_policies)) to construct ReleaseComplianceData and assign to compliance_data, but ensure the Err branch logs the error and returns None (or appropriate fallback) instead of swallowing it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@cli/src/release/manifest_generator.rs`:
- Around line 768-808: The current chain uses
scan_all_compliance(&modules_root).ok().map(...) which silently discards errors
and omits compliance_data; change this to capture the Result from
scan_all_compliance and, on Err(e), emit a warning (e.g., tracing::warn! or the
crate's logger) that includes the error before proceeding so failures are
visible; keep the existing mapping logic for Ok((field_classifications,
retention_policies)) to construct ReleaseComplianceData and assign to
compliance_data, but ensure the Err branch logs the error and returns None (or
appropriate fallback) instead of swallowing it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9d7c01cc-42d0-4e97-8acf-bb7bbda5af09
📒 Files selected for processing (5)
cli/src/compliance/audit.rscli/src/core/manifest.rscli/src/core/validate.rscli/src/release/manifest_generator.rscli/src/sync/all.rs
💤 Files with no reviewable changes (1)
- cli/src/core/validate.rs
Summary by CodeRabbit
New Features
Enhancements
Tests / Docs