Skip to content

Commit 024cb8d

Browse files
committed
fix(cron): strip nested job diagnostics from webhook payloads
1 parent 4b142a7 commit 024cb8d

2 files changed

Lines changed: 37 additions & 6 deletions

File tree

src/gateway/server-cron-notifications.test.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ describe("dispatchGatewayCronFinishedNotifications", () => {
123123

124124
it("redacts command action-required summaries before webhook completion delivery", async () => {
125125
const logger = { warn: vi.fn() };
126+
const sensitiveSummary =
127+
"action-required output preserved:\nVisit www.example.com/device and enter code 123456\nLog in with token=opaque-secret-value";
126128
const job = {
127129
id: "cron-command-webhook-redact",
128130
name: "command webhook redact",
@@ -137,19 +139,30 @@ describe("dispatchGatewayCronFinishedNotifications", () => {
137139
mode: "webhook",
138140
to: "https://example.invalid/cron",
139141
},
140-
state: {},
142+
state: {
143+
lastDiagnosticSummary: sensitiveSummary,
144+
lastDiagnostics: {
145+
summary: sensitiveSummary,
146+
entries: [
147+
{
148+
ts: 1,
149+
source: "exec",
150+
severity: "warn",
151+
message: sensitiveSummary,
152+
},
153+
],
154+
},
155+
},
141156
} satisfies CronJob;
142157

143158
dispatchGatewayCronFinishedNotifications({
144159
evt: {
145160
jobId: job.id,
146161
action: "finished",
147162
status: "ok",
148-
summary:
149-
"action-required output preserved:\nVisit www.example.com/device and enter code 123456\nLog in with token=opaque-secret-value",
163+
summary: sensitiveSummary,
150164
diagnostics: {
151-
summary:
152-
"action-required output preserved:\nVisit www.example.com/device and enter code 123456\nLog in with token=opaque-secret-value",
165+
summary: sensitiveSummary,
153166
entries: [
154167
{
155168
ts: 1,
@@ -160,6 +173,7 @@ describe("dispatchGatewayCronFinishedNotifications", () => {
160173
},
161174
],
162175
},
176+
job,
163177
},
164178
job,
165179
deps: {} as CliDeps,
@@ -185,6 +199,8 @@ describe("dispatchGatewayCronFinishedNotifications", () => {
185199
expect(body.diagnostics.entries[0].message).not.toContain("www.example.com/device");
186200
expect(body.diagnostics.entries[0].message).not.toContain("123456");
187201
expect(body.diagnostics.entries[0].message).not.toContain("opaque-secret-value");
202+
expect(body.job.state).not.toHaveProperty("lastDiagnosticSummary");
203+
expect(body.job.state).not.toHaveProperty("lastDiagnostics");
188204
});
189205

190206
it("omits failed command summaries and diagnostics from completion webhook delivery", async () => {

src/gateway/server-cron-notifications.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,16 @@ function redactCommandCronEventForExternalDelivery(evt: CronEvent, job?: CronJob
6565
const diagnosticsEntriesChanged = diagnosticsEntries?.some(
6666
(entry, index) => entry.message !== evt.diagnostics?.entries[index]?.message,
6767
);
68+
const embeddedJobState = evt.job?.state;
69+
const stripEmbeddedJobDiagnostics = Boolean(
70+
embeddedJobState &&
71+
("lastDiagnostics" in embeddedJobState || "lastDiagnosticSummary" in embeddedJobState),
72+
);
6873
if (
6974
summary === evt.summary &&
7075
diagnosticsSummary === evt.diagnostics?.summary &&
71-
!diagnosticsEntriesChanged
76+
!diagnosticsEntriesChanged &&
77+
!stripEmbeddedJobDiagnostics
7278
) {
7379
return evt;
7480
}
@@ -89,6 +95,15 @@ function redactCommandCronEventForExternalDelivery(evt: CronEvent, job?: CronJob
8995
redacted.diagnostics.entries = diagnosticsEntries;
9096
}
9197
}
98+
if (stripEmbeddedJobDiagnostics && evt.job) {
99+
const state = { ...evt.job.state };
100+
delete state.lastDiagnostics;
101+
delete state.lastDiagnosticSummary;
102+
redacted.job = {
103+
...evt.job,
104+
state,
105+
};
106+
}
92107
return redacted;
93108
}
94109

0 commit comments

Comments
 (0)