Skip to content

Commit 8d4d67b

Browse files
unity-cli@v1.8.2 (#67)
- properly mask sensitive strings when `--verbose` is enabled in cli --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1 parent 89e7372 commit 8d4d67b

7 files changed

Lines changed: 875 additions & 327 deletions

File tree

package-lock.json

Lines changed: 399 additions & 310 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rage-against-the-pixel/unity-cli",
3-
"version": "1.8.1",
3+
"version": "1.8.2",
44
"description": "A command line utility for the Unity Game Engine.",
55
"author": "RageAgainstThePixel",
66
"license": "MIT",
@@ -50,17 +50,17 @@
5050
"dependencies": {
5151
"@electron/asar": "^4.0.1",
5252
"@rage-against-the-pixel/unity-releases-api": "^1.0.4",
53-
"commander": "^14.0.2",
53+
"commander": "^14.0.3",
5454
"glob": "^11.1.0",
55-
"semver": "^7.7.3",
55+
"semver": "^7.7.4",
5656
"source-map-support": "^0.5.21",
57-
"tar": "^7.5.2",
57+
"tar": "^7.5.7",
5858
"update-notifier": "^7.3.1",
5959
"yaml": "^2.8.2"
6060
},
6161
"devDependencies": {
6262
"@types/jest": "^30.0.0",
63-
"@types/node": "^24.10.4",
63+
"@types/node": "^24.10.12",
6464
"@types/semver": "^7.7.1",
6565
"@types/update-notifier": "^6.0.8",
6666
"jest": "^30.2.0",

src/cli.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ program.command('activate-license')
5050
Logger.instance.logLevel = LogLevel.DEBUG;
5151
}
5252

53-
Logger.instance.debug(JSON.stringify(options));
53+
Logger.instance.debugOptions(options);
5454

5555
const client = new LicensingClient();
5656
const licenseStr: string = options.license?.toString()?.trim();
@@ -79,6 +79,11 @@ program.command('activate-license')
7979
if (licenseType === LicenseType.professional && !options.serial) {
8080
options.serial = await PromptForSecretInput('Serial: ');
8181
}
82+
83+
// Mask credentials in CI environments before any potential logging
84+
Logger.instance.maskCredential(options.email);
85+
Logger.instance.maskCredential(options.password);
86+
Logger.instance.maskCredential(options.serial);
8287
}
8388

8489
const token = await client.Activate({
@@ -106,7 +111,7 @@ program.command('return-license')
106111
Logger.instance.logLevel = LogLevel.DEBUG;
107112
}
108113

109-
Logger.instance.debug(JSON.stringify(options));
114+
Logger.instance.debugOptions(options);
110115

111116
const client = new LicensingClient();
112117
const licenseStr: string = options.license?.toString()?.trim();
@@ -134,6 +139,9 @@ program.command('return-license')
134139
Logger.instance.error('Token is required when returning a floating license. Use -t or --token to specify it.');
135140
process.exit(1);
136141
}
142+
143+
// Mask token in CI environments before any potential logging
144+
Logger.instance.maskCredential(token);
137145
}
138146

139147
await client.Deactivate(licenseType, token);
@@ -220,7 +228,7 @@ program.command('hub-install')
220228
Logger.instance.logLevel = LogLevel.DEBUG;
221229
}
222230

223-
Logger.instance.debug(JSON.stringify(options));
231+
Logger.instance.debugOptions(options);
224232

225233
if (options.autoUpdate === true && options.hubVersion) {
226234
Logger.instance.error('Cannot use --auto-update with --hub-version.');
@@ -252,7 +260,7 @@ program.command('hub')
252260
Logger.instance.logLevel = LogLevel.DEBUG;
253261
}
254262

255-
Logger.instance.debug(JSON.stringify({ args, options }));
263+
Logger.instance.debugOptions({ args, options });
256264

257265
const unityHub = new UnityHub();
258266
const output = await unityHub.Exec(args, { silent: false, showCommand: Logger.instance.logLevel === LogLevel.DEBUG });
@@ -280,7 +288,7 @@ program.command('setup-unity')
280288
Logger.instance.logLevel = LogLevel.DEBUG;
281289
}
282290

283-
Logger.instance.debug(JSON.stringify(options));
291+
Logger.instance.debugOptions(options);
284292

285293
let unityProject: UnityProject | undefined;
286294

@@ -373,7 +381,7 @@ program.command('uninstall-unity')
373381
Logger.instance.logLevel = LogLevel.DEBUG;
374382
}
375383

376-
Logger.instance.debug(JSON.stringify(options));
384+
Logger.instance.debugOptions(options);
377385

378386
let unityEditor: UnityEditor | undefined;
379387
const unityVersionStr = options.unityVersion?.toString()?.trim();
@@ -472,7 +480,7 @@ program.command('run')
472480
Logger.instance.logLevel = requestedLogLevel;
473481
}
474482

475-
Logger.instance.debug(JSON.stringify({ options, args }));
483+
Logger.instance.debugOptions({ options, args });
476484

477485
let unityEditor: UnityEditor | undefined;
478486
const editorPath = options.unityEditor?.toString()?.trim() || process.env.UNITY_EDITOR_PATH || undefined;
@@ -540,7 +548,7 @@ program.command('list-project-templates')
540548
Logger.instance.logLevel = LogLevel.DEBUG;
541549
}
542550

543-
Logger.instance.debug(JSON.stringify(options));
551+
Logger.instance.debugOptions(options);
544552

545553
const unityVersionStr = options.unityVersion?.toString()?.trim();
546554

@@ -593,7 +601,7 @@ program.command('create-project')
593601
Logger.instance.logLevel = LogLevel.DEBUG;
594602
}
595603

596-
Logger.instance.debug(JSON.stringify(options));
604+
Logger.instance.debugOptions(options);
597605

598606
const unityVersionStr = options.unityVersion?.toString()?.trim();
599607

@@ -665,7 +673,7 @@ program.command('open-project')
665673
Logger.instance.logLevel = LogLevel.DEBUG;
666674
}
667675

668-
Logger.instance.debug(JSON.stringify(options));
676+
Logger.instance.debugOptions(options);
669677
const projectPath = options.unityProject?.toString()?.trim() || process.env.UNITY_PROJECT_PATH || undefined;
670678
const unityProject = await UnityProject.GetProject(projectPath);
671679

@@ -731,7 +739,7 @@ program.command('sign-package')
731739
Logger.instance.logLevel = LogLevel.DEBUG;
732740
}
733741

734-
Logger.instance.debug(JSON.stringify(options));
742+
Logger.instance.debugOptions(options);
735743

736744
const packagePath = path.normalize(options.package?.toString()?.trim());
737745

@@ -796,6 +804,11 @@ program.command('sign-package')
796804
process.exit(1);
797805
}
798806

807+
// Mask credentials in CI environments before any potential logging
808+
Logger.instance.maskCredential(username);
809+
Logger.instance.maskCredential(password);
810+
Logger.instance.maskCredential(organization);
811+
799812
// must use a unity editor 6000.3 or newer
800813
const unityVersion = new UnityVersion('6000.3');
801814
const unityHub = new UnityHub();

src/logging.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,87 @@ export class Logger {
225225
}
226226
}
227227

228+
/**
229+
* Masks a credential value in CI environments before it appears in logs.
230+
* This is a convenience wrapper around CI_mask for credential values.
231+
* @param value The credential value to mask.
232+
*/
233+
public maskCredential(value: string | undefined): void {
234+
if (value && value.length > 0) {
235+
this.CI_mask(value);
236+
}
237+
}
238+
239+
/**
240+
* Logs command-line options with sensitive information scrubbed.
241+
* Automatically removes passwords, tokens, emails, and other credentials from the output.
242+
* @param options The options object to log (typically from commander.js).
243+
* @param optionalParams Additional parameters to log.
244+
*/
245+
public debugOptions(options: any, ...optionalParams: any[]): void {
246+
// Avoid expensive scrubbing and stringification when debug logging is disabled.
247+
if (this.logLevel !== LogLevel.DEBUG) {
248+
return;
249+
}
250+
const scrubbed = this.scrubSensitiveData(options);
251+
this.debug(JSON.stringify(scrubbed), ...optionalParams);
252+
}
253+
254+
/**
255+
* List of sensitive option keys that should be scrubbed from debug output.
256+
*/
257+
private readonly SENSITIVE_KEYS = [
258+
'password',
259+
'email',
260+
'serial',
261+
'token',
262+
'config',
263+
'organization',
264+
'username',
265+
'servicesConfig'
266+
];
267+
268+
/**
269+
* Scrubs sensitive information from an object for safe logging.
270+
* Creates a deep clone of the object and replaces sensitive values with [REDACTED].
271+
* @param obj The object to scrub (typically command-line options).
272+
* @returns A new object with sensitive values replaced.
273+
*/
274+
private scrubSensitiveData(obj: any): any {
275+
if (obj === null || obj === undefined) {
276+
return obj;
277+
}
278+
279+
if (typeof obj !== 'object') {
280+
return obj;
281+
}
282+
283+
if (Array.isArray(obj)) {
284+
return obj.map((item: any) => this.scrubSensitiveData(item));
285+
}
286+
287+
const scrubbedObj: any = {};
288+
289+
for (const key in obj) {
290+
if (obj.hasOwnProperty(key)) {
291+
const lowerKey = key.toLowerCase();
292+
const isSensitive = this.SENSITIVE_KEYS.some(
293+
sensitiveKey => lowerKey.includes(sensitiveKey.toLowerCase())
294+
);
295+
296+
if (isSensitive) {
297+
scrubbedObj[key] = '[REDACTED]';
298+
} else if (typeof obj[key] === 'object') {
299+
scrubbedObj[key] = this.scrubSensitiveData(obj[key]);
300+
} else {
301+
scrubbedObj[key] = obj[key];
302+
}
303+
}
304+
}
305+
306+
return scrubbedObj;
307+
}
308+
228309
/**
229310
* Sets an environment variable in CI environments that support it.
230311
* @param name The name of the environment variable.

src/unity-editor.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,32 @@ export class UnityEditor {
191191
return templates;
192192
}
193193

194+
/**
195+
* Scrubs sensitive command-line arguments for safe logging.
196+
* Replaces values for sensitive flags like -username, -password, etc. with [REDACTED].
197+
* @param args The command-line arguments array.
198+
* @returns A new array with sensitive values redacted.
199+
*/
200+
public scrubSensitiveArgs(args: string[]): string[] {
201+
const sensitiveFlags = ['-username', '-password', '-cloudOrganization', '-serial'];
202+
const scrubbedArgs: string[] = [];
203+
204+
for (let i = 0; i < args.length; i++) {
205+
const arg = args[i];
206+
if (!arg) continue;
207+
208+
scrubbedArgs.push(arg);
209+
210+
// If this is a sensitive flag and the next item is its value
211+
if (sensitiveFlags.includes(arg) && i + 1 < args.length) {
212+
scrubbedArgs.push('[REDACTED]');
213+
i++; // Skip the next item (the actual value) since we've already added [REDACTED]
214+
}
215+
}
216+
217+
return scrubbedArgs;
218+
}
219+
194220
/**
195221
* Run the Unity Editor with the specified command line arguments.
196222
* @param command The command containing arguments and optional project path.
@@ -264,7 +290,10 @@ export class UnityEditor {
264290

265291
const logPath: string = GetArgumentValueAsString('-logFile', command.args);
266292
logTail = TailLogFile(logPath, command.projectPath);
267-
const commandStr = `\x1b[34m${this.editorPath} ${command.args.join(' ')}\x1b[0m`;
293+
294+
// Scrub sensitive arguments before logging
295+
const scrubbedArgs = this.scrubSensitiveArgs(command.args);
296+
const commandStr = `\x1b[34m${this.editorPath} ${scrubbedArgs.join(' ')}\x1b[0m`;
268297
this.logger.startGroup(commandStr);
269298

270299
if (this.version.isLegacy() && process.platform === 'darwin' && process.arch === 'arm64') {

0 commit comments

Comments
 (0)