Skip to content

Commit 2ebe113

Browse files
committed
fix(deploy-tooling): prevent cleartext credentials in deploy configuration
1 parent ff1c0d8 commit 2ebe113

3 files changed

Lines changed: 99 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sap-ux/deploy-tooling": patch
3+
---
4+
5+
fix(deploy-tooling): prevent cleartext credentials in deploy configuration

packages/deploy-tooling/src/base/config.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ function validateTarget(target: AbapTarget): AbapTarget {
4949
return target;
5050
}
5151

52+
/**
53+
* Validates that credentials are provided as environment variable references, not as plain text.
54+
*
55+
* @param credentials - credentials to validate
56+
*/
57+
function validateCredentials(credentials: AbapDeployConfig['credentials']): void {
58+
const isEnvRef = (value: string | undefined): boolean => !value || value.startsWith('env:');
59+
if (credentials && (!isEnvRef(credentials.username) || !isEnvRef(credentials.password))) {
60+
throw new Error(
61+
'Credentials must be provided as environment variable references (e.g. env:MY_VAR), not as plain text.'
62+
);
63+
}
64+
}
65+
5266
/**
5367
* Type checking the config object.
5468
*
@@ -87,6 +101,9 @@ export function validateConfig(config: AbapDeployConfig | undefined, logger?: Lo
87101
if (!config.app) {
88102
throwConfigMissingError('app');
89103
}
104+
if (config.credentials) {
105+
validateCredentials(config.credentials);
106+
}
90107

91108
return config;
92109
}

packages/deploy-tooling/test/unit/base/config.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,82 @@ describe('base/config', () => {
131131
expect(() => validateConfig(config)).not.toThrow();
132132
expect(config.app.package).toBe('$TMP');
133133
});
134+
135+
describe('validateCredentials', () => {
136+
test('throws when username is plaintext (no env: prefix)', () => {
137+
// given a config with a plaintext username
138+
const config = {
139+
...validConfig,
140+
credentials: { username: 'admin', password: 'env:MY_PASSWORD' }
141+
} as AbapDeployConfig;
142+
143+
// when validateConfig is called
144+
// then it throws with the expected message
145+
expect(() => validateConfig(config)).toThrow(
146+
'Credentials must be provided as environment variable references'
147+
);
148+
});
149+
150+
test('throws when password is plaintext (no env: prefix)', () => {
151+
// given a config with a plaintext password
152+
const config = {
153+
...validConfig,
154+
credentials: { username: 'env:MY_USER', password: 'secret' }
155+
} as AbapDeployConfig;
156+
157+
// when validateConfig is called
158+
// then it throws with the expected message
159+
expect(() => validateConfig(config)).toThrow(
160+
'Credentials must be provided as environment variable references'
161+
);
162+
});
163+
164+
test('throws when both username and password are plaintext', () => {
165+
// given a config with plaintext username and password
166+
const config = {
167+
...validConfig,
168+
credentials: { username: 'admin', password: 'secret' }
169+
} as AbapDeployConfig;
170+
171+
// when validateConfig is called
172+
// then it throws with the expected message
173+
expect(() => validateConfig(config)).toThrow(
174+
'Credentials must be provided as environment variable references'
175+
);
176+
});
177+
178+
test('passes when both username and password use env: prefix', () => {
179+
// given a config with env-referenced credentials
180+
const config = {
181+
...validConfig,
182+
credentials: { username: 'env:MY_USER', password: 'env:MY_PASSWORD' }
183+
} as AbapDeployConfig;
184+
185+
// when validateConfig is called
186+
// then it does not throw
187+
expect(() => validateConfig(config)).not.toThrow();
188+
});
189+
190+
test('passes when credentials are absent', () => {
191+
// given a config without credentials
192+
const config = { ...validConfig } as AbapDeployConfig;
193+
194+
// when validateConfig is called
195+
// then it does not throw
196+
expect(() => validateConfig(config)).not.toThrow();
197+
});
198+
199+
test('passes when only username is set with env: prefix and password is absent', () => {
200+
// given a config with only username set via env:
201+
const config = {
202+
...validConfig,
203+
credentials: { username: 'env:MY_USER' }
204+
} as unknown as AbapDeployConfig;
205+
206+
// when validateConfig is called
207+
// then it does not throw
208+
expect(() => validateConfig(config)).not.toThrow();
209+
});
210+
});
134211
});
135212
});

0 commit comments

Comments
 (0)