Skip to content

Commit e16880c

Browse files
committed
fix: harden config loader against partial config reads
A concurrent rewrite of the unified config file (e.g. a config-mutating test clearing the memoized cache while a background task re-reads) could make yaml.load return null/partial. loadConfigForVersion then dereferenced unifiedConfig.V1/V2 directly and threw "Cannot read properties of undefined (reading 'MIRROR_DB')", which propagated through getConfigV2 -> getHomeOrg into unrelated API handlers and surfaced as intermittent 400s. Fall back to defaults when the parse is null/non-object and optional-chain the version sections so a transient bad read degrades gracefully instead of crashing callers.
1 parent 2ea65d3 commit e16880c

1 file changed

Lines changed: 16 additions & 13 deletions

File tree

src/utils/config-loader.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ const loadConfigForVersion = (dataModelVersion) => {
7777
// Load existing unified config
7878
try {
7979
const yml = yaml.load(fs.readFileSync(unifiedConfigFile, 'utf8'));
80-
unifiedConfig = yml;
80+
// A concurrent rewrite of the config file can yield an empty or partial
81+
// read; fall back to defaults so a null/partial parse cannot propagate a
82+
// TypeError into unrelated callers (e.g. getHomeOrg -> API handlers).
83+
unifiedConfig = yml && typeof yml === 'object' ? yml : { ...defaultConfig };
8184
} catch (error) {
8285
console.error(`Error loading unified config file: ${error.message}`);
8386
unifiedConfig = { ...defaultConfig };
@@ -114,25 +117,25 @@ const loadConfigForVersion = (dataModelVersion) => {
114117
// Merge APP + V1 sections for V1, keeping APP nested
115118
mergedConfig = {
116119
APP: { ...unifiedConfig.APP },
117-
MIRROR_DB: { ...unifiedConfig.V1.MIRROR_DB },
118-
GOVERNANCE: { ...unifiedConfig.V1.GOVERNANCE },
120+
MIRROR_DB: { ...unifiedConfig.V1?.MIRROR_DB },
121+
GOVERNANCE: { ...unifiedConfig.V1?.GOVERNANCE },
119122
// V1-specific top-level values
120-
ENABLE: unifiedConfig.V1.ENABLE,
121-
READ_ONLY: unifiedConfig.V1.READ_ONLY,
122-
CADT_API_KEY: unifiedConfig.V1.CADT_API_KEY,
123-
IS_GOVERNANCE_BODY: unifiedConfig.V1.IS_GOVERNANCE_BODY,
123+
ENABLE: unifiedConfig.V1?.ENABLE,
124+
READ_ONLY: unifiedConfig.V1?.READ_ONLY,
125+
CADT_API_KEY: unifiedConfig.V1?.CADT_API_KEY,
126+
IS_GOVERNANCE_BODY: unifiedConfig.V1?.IS_GOVERNANCE_BODY,
124127
};
125128
} else if (dataModelVersion === 'v2') {
126129
// Merge APP + V2 sections for V2, keeping APP nested
127130
mergedConfig = {
128131
APP: { ...unifiedConfig.APP },
129-
MIRROR_DB: { ...unifiedConfig.V2.MIRROR_DB },
130-
GOVERNANCE: { ...unifiedConfig.V2.GOVERNANCE },
132+
MIRROR_DB: { ...unifiedConfig.V2?.MIRROR_DB },
133+
GOVERNANCE: { ...unifiedConfig.V2?.GOVERNANCE },
131134
// V2-specific top-level values
132-
ENABLE: unifiedConfig.V2.ENABLE,
133-
READ_ONLY: unifiedConfig.V2.READ_ONLY,
134-
CADT_API_KEY: unifiedConfig.V2.CADT_API_KEY,
135-
IS_GOVERNANCE_BODY: unifiedConfig.V2.IS_GOVERNANCE_BODY,
135+
ENABLE: unifiedConfig.V2?.ENABLE,
136+
READ_ONLY: unifiedConfig.V2?.READ_ONLY,
137+
CADT_API_KEY: unifiedConfig.V2?.CADT_API_KEY,
138+
IS_GOVERNANCE_BODY: unifiedConfig.V2?.IS_GOVERNANCE_BODY,
136139
};
137140
} else {
138141
// Fallback to APP section only

0 commit comments

Comments
 (0)