Skip to content

Commit 69a98dc

Browse files
committed
hostname mismatch protection on relevant config sources only
1 parent 9902d76 commit 69a98dc

3 files changed

Lines changed: 105 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@salesforce/b2c-tooling-sdk': patch
3+
---
4+
5+
Fix `--server` override dropping config from non-instance-bound sources. Previously, overriding the server hostname discarded all config values including credentials from global sources like config plugins. Now only values from the source that provided the conflicting hostname are dropped.

packages/b2c-tooling-sdk/src/config/resolver.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export class ConfigResolver {
162162
const sourceInfos: ConfigSourceInfo[] = [];
163163
const sourceWarnings: ConfigWarning[] = [];
164164
const baseConfig: NormalizedConfig = {};
165+
const hostnameProtection = options.hostnameProtection !== false;
165166

166167
// Create enriched options that will be updated with accumulated config values.
167168
// This allows later sources (like plugins) to use values discovered by earlier sources (like dw.json).
@@ -188,6 +189,44 @@ export class ConfigResolver {
188189
const {config: sourceConfig, location} = result;
189190
const fields = getPopulatedFields(sourceConfig);
190191
if (fields.length > 0) {
192+
// Early hostname mismatch detection: if this source provides a hostname
193+
// that conflicts with the override, skip this source entirely.
194+
// This prevents instance-bound sources from blocking fields in later
195+
// non-instance-bound sources (e.g., password-store providing shortCode).
196+
if (
197+
hostnameProtection &&
198+
overrides.hostname &&
199+
sourceConfig.hostname &&
200+
sourceConfig.hostname !== overrides.hostname
201+
) {
202+
sourceWarnings.push({
203+
code: 'HOSTNAME_MISMATCH',
204+
message: `Server override "${overrides.hostname}" differs from config file "${sourceConfig.hostname}". Config file values ignored.`,
205+
details: {
206+
providedHostname: overrides.hostname,
207+
configHostname: sourceConfig.hostname,
208+
},
209+
});
210+
211+
sourceInfos.push({
212+
name: source.name,
213+
location,
214+
fields: [],
215+
fieldsIgnored: fields,
216+
});
217+
218+
const logger = getLogger();
219+
logger.trace(
220+
{
221+
source: source.name,
222+
location,
223+
fieldsIgnored: fields,
224+
},
225+
`[${source.name}] Skipped due to hostname mismatch`,
226+
);
227+
continue;
228+
}
229+
191230
// Capture which credential groups are already claimed BEFORE processing this source
192231
// This allows a single source to provide complete credential pairs
193232
const claimedGroups = getClaimedCredentialGroups(baseConfig);
@@ -247,7 +286,10 @@ export class ConfigResolver {
247286
}
248287
}
249288

250-
// Apply overrides with hostname mismatch protection
289+
// Apply overrides with hostname mismatch protection.
290+
// Instance-bound sources with conflicting hostnames were already skipped above,
291+
// so baseConfig only contains non-instance-bound fields. The merge handles
292+
// the case where no source provided a hostname (no mismatch to detect).
251293
const {config, warnings: mergeWarnings} = mergeConfigsWithProtection(overrides, baseConfig, {
252294
hostnameProtection: options.hostnameProtection,
253295
});

packages/b2c-tooling-sdk/test/config/resolver.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,63 @@ describe('config/resolver', () => {
674674
expect(config.selfSigned).to.equal(true);
675675
});
676676

677+
it('preserves non-instance-bound source fields on hostname mismatch', () => {
678+
const instanceSource = new MockSource('dw-json', {
679+
hostname: 'prod.demandware.net',
680+
username: 'admin',
681+
password: 'prod-pass',
682+
shortCode: 'abcdef',
683+
});
684+
const globalSource = new MockSource('password-store', {
685+
clientId: 'my-client-id',
686+
clientSecret: 'my-client-secret',
687+
shortCode: 'abcdef',
688+
});
689+
const resolver = new ConfigResolver([instanceSource, globalSource]);
690+
691+
const {config, warnings, sources} = resolver.resolve(
692+
{hostname: 'staging.demandware.net'},
693+
{hostnameProtection: true},
694+
);
695+
696+
expect(config.hostname).to.equal('staging.demandware.net');
697+
// Instance-bound fields should be dropped
698+
expect(config.username).to.be.undefined;
699+
expect(config.password).to.be.undefined;
700+
// Non-instance-bound fields should survive
701+
expect(config.clientId).to.equal('my-client-id');
702+
expect(config.clientSecret).to.equal('my-client-secret');
703+
// Fields from non-instance-bound source that were previously shadowed
704+
// by the instance-bound source should now be available
705+
expect(config.shortCode).to.equal('abcdef');
706+
expect(warnings).to.have.length(1);
707+
expect(warnings[0].code).to.equal('HOSTNAME_MISMATCH');
708+
709+
// Source info should reflect the drop
710+
const dwJsonInfo = sources.find((s) => s.name === 'dw-json');
711+
expect(dwJsonInfo).to.exist;
712+
expect(dwJsonInfo!.fields).to.deep.equal([]);
713+
expect(dwJsonInfo!.fieldsIgnored).to.include('hostname');
714+
expect(dwJsonInfo!.fieldsIgnored).to.include('username');
715+
expect(dwJsonInfo!.fieldsIgnored).to.include('password');
716+
});
717+
718+
it('drops fields from plugin source that also provides hostname on mismatch', () => {
719+
const pluginSource = new MockSource('custom-plugin', {
720+
hostname: 'prod.demandware.net',
721+
clientId: 'plugin-client-id',
722+
clientSecret: 'plugin-client-secret',
723+
});
724+
const resolver = new ConfigResolver([pluginSource]);
725+
726+
const {config} = resolver.resolve({hostname: 'staging.demandware.net'}, {hostnameProtection: true});
727+
728+
expect(config.hostname).to.equal('staging.demandware.net');
729+
// Plugin provided hostname, so it's instance-bound — all its fields dropped
730+
expect(config.clientId).to.be.undefined;
731+
expect(config.clientSecret).to.be.undefined;
732+
});
733+
677734
it('discards TLS options on hostname mismatch protection', () => {
678735
const source = new MockSource('test', {
679736
hostname: 'prod.demandware.net',

0 commit comments

Comments
 (0)