GeezerVison is showing github.com — fix(cli): stop logging project env var values in the "started attempt" debug log by enderyildirim · Pull Request #4457 · triggerdotdev/trigger.dev
Conversation
Fixes the remaining leak site from #3566.
What I observed
On a self-hosted v4 install, each runner container writes the whole startRunAttempt response body to stdout at the start of every attempt. WorkerApiRunAttemptStartResponseBody is StartRunAttemptResult & { envVars: z.record(z.string()) }, so that object contains the full project environment variable map. Redacted excerpt of a real runner log:
{"timestamp":"...","message":"started attempt","$name":"managed-run-logger","$level":"log",
"start":{"envVars":{
"DATABASE_URL":"postgresql://user:REAL_PASSWORD_WAS_HERE@db:5432/app",
"SOME_PROVIDER_API_KEY":"REAL_KEY_WAS_HERE",
"APP_SIGNING_SECRET":"REAL_SECRET_WAS_HERE"
}, ...}}
Those values landed in every runner container's json log file, one entry per attempt.
Why the existing redaction doesn't catch it
SimpleStructuredLogger passes each line through redact() from packages/core/src/logger.ts, but filterKeys matches on the whole lowercased key name:
if (keys.has(key.toLowerCase())) { ... }
envVars isn't in DEFAULT_FILTERED_KEYS, so it's recursed into, and the user-defined names inside it (DATABASE_URL, SUPABASE_SERVICE_ROLE_KEY, …) match nothing in the list either. That leaves only SECRET_VALUE_PATTERN, which catches tr_*, sk-* and Bearer * — so a Postgres URL, a JWT-shaped service key, or a hex master key all pass through verbatim. The payload and metadata fields on the same object are filtered, which is why this is easy to miss.
ManagedRunLogger.sendDebugLog also forwards the same properties to the webapp debug-log endpoint via flattenAttributes, with no redaction on that path. It's gated behind TRIGGER_SEND_RUN_DEBUG_LOGS (default false), so it only leaks for operators who turned that on — but when they do, unredacted.
Why it matters for self-hosters
Runner stdout goes to whatever collects container logs (Loki, CloudWatch, Datadog, plain docker json files). Those pipelines are usually treated as low-sensitivity and are broadly readable, retained for weeks, and often shipped off-box. A task that needs any credential to do its job ends up publishing that credential there on every single attempt. LOG_LEVEL / DEBUG don't suppress it: sendDebugLog uses SimpleStructuredLogger.log, which is LogLevel.log = 0, the lowest value in the enum, so the if (this.level < LogLevel.log) return guard can never fire.
What I changed
execution.ts now logs an explicit projection instead of the raw response body — new executionLogging.ts, mirroring the buildWorkerLogging.ts split added in #4420 so the payload is unit-testable:
this.sendDebugLog("started attempt", startedAttemptLogProperties(start.data));
which yields run/snapshot identifiers, attempt number, task, queue, machine preset, and envVarKeys — the environment variable names only.
Two deliberate choices:
- Redact rather than remove. The entry is a useful "the attempt started, here is what it started with" marker, and the names answer the common question of whether a variable actually reached the runner. Only the values are dropped.
- Allow-list, not deny-list.
envVars keys are user-defined, so no name-based deny-list can classify them — hence values are dropped wholesale rather than filtered. For the rest of the body an allow-list means a new field on the API response can't silently reintroduce a leak. This matches fix(cli): redact task run env values from debug log #4336, which fixed the equivalent leak in taskRunProcess.ts by logging Object.keys(fullEnv).
Since both sinks share one properties object, this fixes the stdout and webapp-endpoint halves together.
Scope
The other two sites named in #3566 are already handled on main, so this PR leaves them alone:
taskRunProcess.ts — fixed by fix(cli): redact task run env values from debug log #4336 (released in 4.5.7); logs envKeys now.
controller.ts — ...env.raw became ...env.rawForLogging, which redacts TRIGGER_DEPLOYMENT_ID. The rest is the bounded RunnerEnv zod schema, so no project secrets reach it.
One thing I noticed but did not touch, to keep this to a single issue: the debug-log HTTP sink in ManagedRunLogger.sendDebugLog flattens the raw mergedProperties, so it doesn't get the redact() pass that the stdout sink gets from SimpleStructuredLogger. Any other sendDebugLog caller that passes something sensitive is therefore protected on stdout but not on the wire. Happy to open a follow-up if you'd like that sink to redact too.
How I tested
- Added
packages/cli-v3/src/entryPoints/managed/executionLogging.test.ts (4 tests, passing): asserts the serialized properties contain neither an env var value nor the run payload, that the env var names survive, the exact field set, and the envVars-absent case.
- Confirmed the test fails if the projection regresses — adding the raw body back to the returned object turns 2 of the 4 red.
oxlint and oxfmt --check clean on the touched files.
tsc -p tsconfig.src.json --noEmit reports the same 7 errors with and without this change (all from workspace packages I hadn't built), so no new type errors.
- I could not run
snapshot.test.ts / taskRunProcess.test.ts in the same package: they need a built @trigger.dev/core, and building it needs Node 24, which I don't have locally. They fail identically on a clean checkout of main, so this change doesn't affect them, but I have not run the full cli-v3 suite.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
…bug log
The managed run controller logged the raw `startRunAttempt` response body,
which carries `envVars` — the project environment variables injected into the
run — along with the trigger payload and run metadata. That went to the runner
container's stdout on every attempt, and to the webapp debug-log endpoint when
`TRIGGER_SEND_RUN_DEBUG_LOGS` is enabled.
The stdout path runs through `redact()`, but its deny-list matches whole
lowercased key names, so it filters `payload`/`metadata` and leaves every entry
inside the `envVars` map untouched: `DATABASE_URL` or `SUPABASE_SERVICE_ROLE_KEY`
match nothing in the list, and only values shaped like `tr_*`, `sk-*` or
`Bearer *` are caught by the value pattern. The debug-log HTTP sink applies no
redaction at all.
Log an explicit projection instead: run/snapshot identifiers, task, queue and
machine preset, plus the environment variable *names*. Names are the part with
debugging value ("did this var reach the runner?"); the values never are.
Because `envVars` keys are user-defined, no name-based deny-list can classify
them — so values are dropped wholesale rather than filtered. The remaining
fields are an allow-list, so a new field on the API response cannot silently
reintroduce a leak.
Same shape as triggerdotdev#4336, which fixed the equivalent leak in
`taskRunProcess.ts`.
Refs triggerdotdev#3566
Co-Authored-By: Claude Opus 5 <[email protected]>
🦋 Changeset detected
Latest commit: 5c98043
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 27 packages
| Name |
Type |
| trigger.dev |
Patch |
| @internal/dashboard-agent |
Patch |
| @trigger.dev/build |
Patch |
| @trigger.dev/core |
Patch |
| @trigger.dev/python |
Patch |
| @trigger.dev/react-hooks |
Patch |
| @trigger.dev/redis-worker |
Patch |
| @trigger.dev/rsc |
Patch |
| @trigger.dev/schema-to-json |
Patch |
| @trigger.dev/sdk |
Patch |
| @trigger.dev/database |
Patch |
| @trigger.dev/otlp-importer |
Patch |
| @trigger.dev/rbac |
Patch |
| @trigger.dev/sso |
Patch |
| @internal/cache |
Patch |
| @internal/clickhouse |
Patch |
| @internal/llm-model-catalog |
Patch |
| @internal/metrics-pipeline |
Patch |
| @internal/redis |
Patch |
| @internal/replication |
Patch |
| @internal/run-engine |
Patch |
| @internal/run-store |
Patch |
| @internal/schedule-engine |
Patch |
| @internal/testcontainers |
Patch |
| @internal/tracing |
Patch |
| @internal/tsql |
Patch |
| @internal/sdk-compat-tests |
Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Devin Review found 1 potential issue.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
|
*/
|
|
|
export function startedAttemptLogProperties(start: WorkloadRunAttemptStartResponseBody) {
|
|
|
return {
|
|
|
runId: start.run.id,
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Internal run identifier never appears in the attempt log despite being collected
The internal run identifier is added under a name that is later replaced (runId: start.run.id at packages/cli-v3/src/entryPoints/managed/executionLogging.ts:18) by the public run identifier, so that value never reaches the log and the entry shows the same identifier twice.
Impact: Operators reading the "started attempt" entry lose the internal run identifier the change intended to preserve, and see a duplicated value instead.
Property-name collision in the debug-log merge chain
RunExecution.sendDebugLog spreads the caller's properties and then unconditionally sets runId: this.runFriendlyId and snapshotId: this.currentSnapshotFriendlyId (packages/cli-v3/src/entryPoints/managed/execution.ts:1036-1043). ManagedRunLogger.sendDebugLog does the same again with runId (packages/cli-v3/src/entryPoints/managed/logger.ts:47-51). So the projection's runId (the internal DB id) is overwritten and only runFriendlyId survives — which equals the injected runId, making the two fields identical. The unit test passes because it calls startedAttemptLogProperties directly, bypassing both merges. Using a distinct key such as internalRunId would keep the value.
|
runId: start.run.id, |
|
internalRunId: start.run.id, |
Was this helpful? React with 👍 or 👎 to provide feedback.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
coderabbitai
Bot
commented
Aug 1, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
|

Caution
Review failed
The pull request is closed.
ℹ️ Recent review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 984e2720-07af-44c3-b69e-46d1f26a665f
📥 Commits
Reviewing files that changed from the base of the PR and between 14824b0 and 5c98043.
📒 Files selected for processing (4)
.changeset/redact-started-attempt-debug-log.md
packages/cli-v3/src/entryPoints/managed/execution.ts
packages/cli-v3/src/entryPoints/managed/executionLogging.test.ts
packages/cli-v3/src/entryPoints/managed/executionLogging.ts
Walkthrough
The change adds startedAttemptLogProperties to create an allow-listed started-attempt log payload. The payload includes diagnostic metadata and environment-variable names, but excludes environment-variable values. Managed execution uses the helper for the "started attempt" debug log. Tests cover redaction, retained keys, identifiers, and missing environment variables. A patch changeset documents the behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
- Create PR with unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
❤️ Share
Comment @coderabbitai help to get the list of available commands.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|