fix(Runner): AJDA-3007 stop logging decrypted configuration state#805
Conversation
There was a problem hiding this comment.
Pull request overview
This PR prevents decrypted #-prefixed secrets from component state (e.g., OAuth tokens/API keys) from being logged in plaintext at job start by registering those secrets with the existing OutputFilter and redacting the application log message.
Changes:
- Register
JobDefinitionstate secrets withOutputFilterbefore emitting theUsing configuration id… state:notice log line. - Redact the full notice message via
OutputFilter::redactSecrets()(also applying existing message trimming). - Add a unit test that asserts state secrets are redacted (
[hidden]) and not logged in plaintext.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/Docker/Runner.php |
Collects state secrets and redacts the “Using configuration id…” notice message before writing to the application log channel. |
Tests/Runner/RunnerStateRedactionTest.php |
Adds a hermetic unit test ensuring state # secrets are redacted from the configuration log line. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Addressed review feedback in 2f1df22:
Not changed, by design:
|
|
@odinuv vyhodil jsem teda ten state z logu uplne |
keboola-pr-reviewer-bot
left a comment
There was a problem hiding this comment.
Verdict: needs_human (risk 2/5) · profile ajda
Needs a human: this touches docker-bundle's job-execution core (Runner::runRow), which policy escalates regardless of size.
Concerns:
src/Docker/Runner.php: Modifies job-execution core (Runner::runRow) — always-needs-human per policy
Suggested reviewers: @keboola/ajda-swimlane
odinuv
left a comment
There was a problem hiding this comment.
je to mergnutelný, ale šlo by to ještě trošku zjenodušit, pak je potřeba se ještě kouknout jestli není podobnej log někde v no dindu (IM, nebo service container)
… line At job start runRow() logged the component state via json_encode(). By that point the state is already decrypted, so any #-prefixed secret it held (OAuth tokens, API keys) was written in plaintext to the application log channel and shipped to Datadog. That channel is never passed through OutputFilter, and the state secrets are only collected into the filter later in the StateFile constructor - so the existing redaction missed this line entirely. Register the state's #-keyed secrets with the OutputFilter before logging and run the whole message through redactSecrets(), reusing the existing redaction path. This closes both the timing and the channel gap.
…ion test Address PR review: - OutputFilter::addValue now deduplicates filter values. The same state is registered for the log line and again by StateFile, once per job row, so the filter list would otherwise grow unbounded across rows and make redactSecrets() do redundant work on every output line. - RunnerStateRedactionTest: narrow the catch to RuntimeException and assert its message so the test fails loudly if a different path is reached; add explicit mock call-count expectations per AJDA PHPUnit conventions.
… of redacting Per review (Odin): logging the configuration state is not necessary, so removing it is simpler and safer than redacting. Drops the state from the 'Using configuration id' notice entirely and reverts the OutputFilter redaction/dedup changes that were only needed to support redaction. Test renamed to assert the state (and its secrets) is not present in the log line.
… drop comment Per review (Odin): - Remove the explanatory comment above the log line (would go stale). - Drop the standalone RunnerStateLoggingTest and instead assert in the existing testExecutorReadNamespacedState that the 'Using configuration id' line does not contain the state - avoids booting a whole extra runner test for one assert.
a44f31c to
2c4917a
Compare
Summary
At job start,
Runner::runRow()logged the component state viajson_encode($jobDefinition->getState()). By that point the state is already decrypted (upstream injob-runner'sJobDefinitionFactory::decryptConfiguration()), so any#-prefixed secret it holds — OAuth access/refresh tokens, API keys — was written in plaintext to the application log channel and shipped to Datadog (hot index + S3 archive). The application log channel (getLog()) is never passed throughOutputFilter, so the existing secret redaction never covered this line.Fix
The configuration state is not useful in this log line, so it is simply removed rather than redacted (per review). The
Using configuration id …notice keeps the non-sensitive fields (config id, version, row id, tmp folder):Test
Added
Tests/Runner/RunnerStateLoggingTest.php— a hermetic unit test (no Docker/network) that drivesRunner::run()with a#-secret in state, halts execution right after the log line, and asserts theUsing configuration idrecord contains neitherstate:nor the plaintext secret (and still logs the non-sensitive fields).Linear: AJDA-3007
Release Notes
Justification
Plans for Customer Communication
Impact Analysis
Using configuration id …notice line no longer includes the state. No functional/runtime behavior changes. Affects all tenants (the log line runs for every job row) but only changes what is written to logs. Not behind a feature flag (there is no behavior worth toggling — gating would mean conditionally continuing to leak secrets).Deployment Plan
docker-bundlerelease →job-runnerbump. Not critical; no ordering constraints.Rollback Plan
docker-bundleversion. No data or schema changes.Post-Release Support Plan
Using configuration id …line no longer containsstate:for a component that stores OAuth tokens in state (e.g.keboola.ex-onedrive).