From c882ca6dc7bc78a49849153df81234fca3bc6e59 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:15:18 +0100 Subject: [PATCH 1/2] chore(core): redact sensitive flag values from exec command logs --- .changeset/tidy-exec-arg-logging.md | 5 +++ packages/core/src/v3/apps/exec.test.ts | 26 +++++++++++++++ packages/core/src/v3/apps/exec.ts | 45 ++++++++++++++++++++++++-- 3 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 .changeset/tidy-exec-arg-logging.md create mode 100644 packages/core/src/v3/apps/exec.test.ts diff --git a/.changeset/tidy-exec-arg-logging.md b/.changeset/tidy-exec-arg-logging.md new file mode 100644 index 00000000000..6f96d23ff13 --- /dev/null +++ b/.changeset/tidy-exec-arg-logging.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/core": patch +--- + +Redact credential-bearing flag values (e.g. `--password`, `--token`) from `Exec` command debug logs diff --git a/packages/core/src/v3/apps/exec.test.ts b/packages/core/src/v3/apps/exec.test.ts new file mode 100644 index 00000000000..898de3710fb --- /dev/null +++ b/packages/core/src/v3/apps/exec.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, it } from "vitest"; +import { redactArgsForLogging } from "./exec.js"; + +describe("redactArgsForLogging", () => { + it("masks the value following a credential flag", () => { + expect( + redactArgsForLogging(["login", "--username", "robot", "--password", "s3cr3t", "host:80"]) + ).toEqual(["login", "--username", "robot", "--password", "[redacted]", "host:80"]); + }); + + it("masks inline --flag=value form", () => { + expect(redactArgsForLogging(["--token=abc123"])).toEqual(["--token=[redacted]"]); + }); + + it("leaves non-credential args untouched", () => { + expect(redactArgsForLogging(["push", "--tls-verify=false", "host:80/img"])).toEqual([ + "push", + "--tls-verify=false", + "host:80/img", + ]); + }); + + it("passes undefined through", () => { + expect(redactArgsForLogging(undefined)).toBeUndefined(); + }); +}); diff --git a/packages/core/src/v3/apps/exec.ts b/packages/core/src/v3/apps/exec.ts index 2ebd977e977..eac8fa0c58f 100644 --- a/packages/core/src/v3/apps/exec.ts +++ b/packages/core/src/v3/apps/exec.ts @@ -23,6 +23,38 @@ export interface ExecOptions { neverThrow?: boolean; } +// Long-form flags whose value carries a credential - the following arg (or inline +// `--flag=value`) is replaced before args are logged so it never reaches log sinks. +const REDACTED_FLAGS = new Set([ + "--password", + "--token", + "--secret", + "--access-token", + "--registry-token", + "--registry-password", + "--api-key", +]); + +export function redactArgsForLogging(args?: string[]): string[] | undefined { + if (!args) { + return args; + } + + return args.map((arg, index) => { + const previous = index > 0 ? args[index - 1]?.trim() : undefined; + if (previous && REDACTED_FLAGS.has(previous)) { + return "[redacted]"; + } + + const equalsIndex = arg.indexOf("="); + if (equalsIndex > 0 && REDACTED_FLAGS.has(arg.slice(0, equalsIndex).trim())) { + return `${arg.slice(0, equalsIndex)}=[redacted]`; + } + + return arg; + }); +} + export class Exec { private logger: SimpleStructuredLogger; private abortSignal: AbortSignal | undefined; @@ -47,8 +79,15 @@ export class Exec { ): Promise { const argsTrimmed = this.trimArgs ? args?.map((arg) => arg.trim()) : args; + const argsForLogging = redactArgsForLogging(args); + const argsTrimmedForLogging = redactArgsForLogging(argsTrimmed); + const commandWithFirstArg = `${command}${argsTrimmed?.length ? ` ${argsTrimmed[0]}` : ""}`; - this.logger.debug(`exec: ${commandWithFirstArg}`, { command, args, argsTrimmed }); + this.logger.debug(`exec: ${commandWithFirstArg}`, { + command, + args: argsForLogging, + argsTrimmed: argsTrimmedForLogging, + }); const result = x(command, argsTrimmed, { signal: opts?.ignoreAbort ? undefined : this.abortSignal, @@ -60,8 +99,8 @@ export class Exec { const metadata = { command, - argsRaw: args, - argsTrimmed, + argsRaw: argsForLogging, + argsTrimmed: argsTrimmedForLogging, globalOpts: { trimArgs: this.trimArgs, neverThrow: this.neverThrow, From 7d948f8fecd6fbdace68e49647008194f0be92e1 Mon Sep 17 00:00:00 2001 From: nicktrn <55853254+nicktrn@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:21:07 +0100 Subject: [PATCH 2/2] chore(core): redact first arg in exec log message text --- packages/core/src/v3/apps/exec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/v3/apps/exec.ts b/packages/core/src/v3/apps/exec.ts index eac8fa0c58f..297e2453da9 100644 --- a/packages/core/src/v3/apps/exec.ts +++ b/packages/core/src/v3/apps/exec.ts @@ -82,7 +82,7 @@ export class Exec { const argsForLogging = redactArgsForLogging(args); const argsTrimmedForLogging = redactArgsForLogging(argsTrimmed); - const commandWithFirstArg = `${command}${argsTrimmed?.length ? ` ${argsTrimmed[0]}` : ""}`; + const commandWithFirstArg = `${command}${argsTrimmedForLogging?.length ? ` ${argsTrimmedForLogging[0]}` : ""}`; this.logger.debug(`exec: ${commandWithFirstArg}`, { command, args: argsForLogging,