Skip to content

Commit b0d8d1a

Browse files
ericsciplebryanmacfarlane
authored andcommitted
Error on multiline secret (microsoft#315)
* Error on multiline secret * v2.3.0
1 parent 1692b8a commit b0d8d1a

File tree

7 files changed

+113
-3
lines changed

7 files changed

+113
-3
lines changed

node/Strings/resources.resjson/en-US/resources.resjson

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"loc.messages.LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'",
66
"loc.messages.LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'",
77
"loc.messages.LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.",
8+
"loc.messages.LIB_MultilineSecret": "Secrets cannot contain multiple lines",
89
"loc.messages.LIB_ReturnCode": "Return code: %d",
910
"loc.messages.LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s",
1011
"loc.messages.LIB_ResourceFileAlreadySet": "Resource file has already set to: %s",

node/docs/releases.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# VSTS-TASK-LIB RELEASES
22

3+
## 2.3.0
4+
* Updated `setVariable` to fail when a secret contains multiple lines.
5+
* Added `setSecret` to register a secret with the log scrubber, without registering a variable. Multi-line secrets are not supported.
6+
37
## 2.0.4-preview
48
* Updated `ToolRunner` to validate the specified tool can be found and is executable.
59
* Updated `which` to validate the file is executable and also on Windows to apply PATHEXT.

node/lib.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"LIB_MkdirFailedFileExists": "Unable to create directory '%s'. Conflicting file exists: '%s'",
77
"LIB_MkdirFailedInvalidDriveRoot": "Unable to create directory '%s'. Root directory does not exist: '%s'",
88
"LIB_MkdirFailedInvalidShare": "Unable to create directory '%s'. Unable to verify the directory exists: '%s'. If directory is a file share, please verify the share name is correct, the share is online, and the current process has permission to access the share.",
9+
"LIB_MultilineSecret": "Secrets cannot contain multiple lines",
910
"LIB_ReturnCode": "Return code: %d",
1011
"LIB_ResourceFileNotExist": "Resource file doesn\\'t exist: %s",
1112
"LIB_ResourceFileAlreadySet": "Resource file has already set to: %s",

node/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vsts-task-lib",
3-
"version": "2.2.1",
3+
"version": "2.3.0",
44
"description": "VSTS Task SDK",
55
"main": "./task.js",
66
"typings": "./task.d.ts",

node/task.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export function getVariables(): VariableInfo[] {
114114
*
115115
* @param name name of the variable to set
116116
* @param val value to set
117-
* @param secret whether variable is secret. optional, defaults to false
117+
* @param secret whether variable is secret. Multi-line secrets are not allowed. Optional, defaults to false
118118
* @returns void
119119
*/
120120
export function setVariable(name: string, val: string, secret: boolean = false): void {
@@ -128,6 +128,10 @@ export function setVariable(name: string, val: string, secret: boolean = false):
128128
let varValue = val || '';
129129
debug('set ' + name + '=' + (secret && varValue ? '********' : varValue));
130130
if (secret) {
131+
if (varValue && varValue.match(/\r|\n/) && `${process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']}`.toUpperCase() != 'TRUE') {
132+
throw new Error(loc('LIB_MultilineSecret'));
133+
}
134+
131135
im._vault.storeSecret('SECRET_' + key, varValue);
132136
delete process.env[key];
133137
} else {
@@ -141,6 +145,20 @@ export function setVariable(name: string, val: string, secret: boolean = false):
141145
command('task.setvariable', { 'variable': name || '', 'issecret': (secret || false).toString() }, varValue);
142146
}
143147

148+
/**
149+
* Registers a value with the logger, so the value will be masked from the logs. Multi-line secrets are not allowed.
150+
*
151+
* @param val value to register
152+
*/
153+
export function setSecret(val: string): void {
154+
if (val) {
155+
if (val.match(/\r|\n/) && `${process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET']}`.toUpperCase() !== 'TRUE') {
156+
throw new Error(loc('LIB_MultilineSecret'));
157+
}
158+
command('task.setsecret', {}, val);
159+
}
160+
}
161+
144162
/** Snapshot of a variable at the time when getVariables was called. */
145163
export interface VariableInfo {
146164
name: string;

node/test/inputtests.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,92 @@ describe('Input Tests', function () {
222222

223223
done();
224224
})
225+
it('does not allow setting a multi-line secret variable', function (done) {
226+
this.timeout(1000);
227+
228+
im._loadData();
229+
230+
// test carriage return
231+
let failed = false;
232+
try {
233+
tl.setVariable('my.secret', 'line1\rline2', true);
234+
}
235+
catch (err) {
236+
failed = true;
237+
}
238+
assert(failed, 'Should have failed setting a secret variable with a carriage return');
239+
240+
// test line feed
241+
failed = false;
242+
try {
243+
tl.setVariable('my.secret', 'line1\nline2', true);
244+
}
245+
catch (err) {
246+
failed = true;
247+
}
248+
assert(failed, 'Should have failed setting a secret variable with a line feed');
249+
250+
done();
251+
})
252+
it('allows unsafe setting a multi-line secret variable', function (done) {
253+
this.timeout(1000);
254+
255+
im._loadData();
256+
try {
257+
process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true';
258+
tl.setVariable('my.secret', 'line1\r\nline2', true);
259+
}
260+
finally {
261+
delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'];
262+
}
263+
264+
assert.equal(tl.getVariable('my.secret'), 'line1\r\nline2');
265+
266+
done();
267+
})
268+
269+
// setSecret tests
270+
it('does not allow setting a multi-line secret', function (done) {
271+
this.timeout(1000);
272+
273+
im._loadData();
274+
275+
// test carriage return
276+
let failed = false;
277+
try {
278+
tl.setSecret('line1\rline2');
279+
}
280+
catch (err) {
281+
failed = true;
282+
}
283+
assert(failed, 'Should have failed setting a secret with a carriage return');
284+
285+
// test line feed
286+
failed = false;
287+
try {
288+
tl.setSecret('line1\nline2');
289+
}
290+
catch (err) {
291+
failed = true;
292+
}
293+
assert(failed, 'Should have failed setting a secret with a line feed');
294+
295+
done();
296+
})
297+
it('allows unsafe setting a multi-line secret', function (done) {
298+
this.timeout(1000);
299+
300+
im._loadData();
301+
try {
302+
process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'] = 'true';
303+
tl.setSecret('line1\r\nline2');
304+
}
305+
finally {
306+
delete process.env['SYSTEM_UNSAFEALLOWMULTILINESECRET'];
307+
}
308+
309+
done();
310+
})
225311

226312
// getVariables tests
227313
it('gets public variables from initial load', function (done) {

node/test/toolrunnertests.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ describe('Toolrunner Tests', function () {
329329
}
330330
})
331331
it('Succeeds on stderr by default', function (done) {
332-
this.timeout(1000);
332+
this.timeout(2000);
333333

334334
var scriptPath = path.join(__dirname, 'scripts', 'stderroutput.js');
335335
var ls = tl.tool(tl.which('node', true));

0 commit comments

Comments
 (0)