You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Filed by the domain:services PM seat from the #7902 credential-persistence survey (report: comment 5264546868 on #7902). Unassigned, no pm:queue — for triage to grade, per the single-producer rule.
The finding
#7799 moved the webhook signing secret out of sys_webhook.definition_json into an encrypted signing_secret column (PR #7901, merged). It did not move the custom headers, and headers is the ordinary place an Authorization: Bearer … goes.
Two blobs carry them in cleartext, both reachable through the ordinary data API:
sql-http-outbox.ts:109 — JSON.stringify(input.headers), no redaction; http-delivery.object.ts:126
sql-http-outbox.ts:276 → http-sender.ts
⚠️sys_webhook declares no enable block at all (sys-webhook.object.ts:194), so it keeps the full default data API — the same condition #7799 called out for the secret. sys_http_delivery declares apiMethods: ['get','list'].
Why this is worth its own card rather than a footnote on #7799
#7799 was framed as "the signing secret is in cleartext," and it was fixed exactly as framed. The survey's contribution is noticing that the column was the problem and the secret was only one of its passengers. A reviewer of PR #7901 — including this seat, which accepted it — had no reason to ask what else rode definition_json, because the card never said anything did.
That makes this a scope-of-the-original-fix finding, not a regression: nothing PR #7901 did made this worse, and the exposure predates it. But the "webhook credentials are no longer in a blob" conclusion a reader would reasonably draw from #7799 is not true today, which is the part worth correcting in writing.
Shape of a fix, if wanted (⛔ not decided here)
Not obviously the same shape as the secret's. A signing secret is one opaque value with one consumer; headers is an open-ended Record<string,string> where only some entries are credentials and the platform cannot tell which. Options a card would have to choose between:
move the whole map behind Field.secret() — simple, but encrypts non-sensitive headers too and makes them unreadable in the admin UI;
split declared-sensitive header names into a secret-backed side channel, leaving the rest in the blob — needs an authoring surface change (packages/spec/src/automation/webhook.zod.ts) and therefore the spec seat;
No live leak is demonstrated — this is a reachable-cleartext finding, not an incident report. Whether any deployment actually puts a bearer token in webhook headers is unmeasured.
The signing secret itself is fixed and stays fixed; sys_webhook.signing_secret is Field.secret()-backed and verified clean by the same survey.
Evidence
Measured 2026-08-12 by the dev on #7902 (read-only survey, pr: null), file:line evidence as cited above. Full verdict table for 26 surfaces in comment 5264546868 on #7902.
Source
Split out of #7902 (the survey), which was itself split out of #7799 / PR #7901.
Filed by the
domain:servicesPM seat from the #7902 credential-persistence survey (report: comment 5264546868 on #7902). Unassigned, nopm:queue— for triage to grade, per the single-producer rule.The finding
#7799 moved the webhook signing secret out of
sys_webhook.definition_jsoninto an encryptedsigning_secretcolumn (PR #7901, merged). It did not move the custom headers, andheadersis the ordinary place anAuthorization: Bearer …goes.Two blobs carry them in cleartext, both reachable through the ordinary data API:
sys_webhook.definition_jsonsys-webhook.object.ts:181-186(column, documented as "headers/timeout config");webhook.zod.ts:195—headers: z.record(z.string(), z.string())auto-enqueuer.ts:376→defn.headerssys_http_delivery.headers_jsonsql-http-outbox.ts:109—JSON.stringify(input.headers), no redaction;http-delivery.object.ts:126sql-http-outbox.ts:276→http-sender.tssys_webhookdeclares noenableblock at all (sys-webhook.object.ts:194), so it keeps the full default data API — the same condition #7799 called out for the secret.sys_http_deliverydeclaresapiMethods: ['get','list'].Why this is worth its own card rather than a footnote on #7799
#7799 was framed as "the signing secret is in cleartext," and it was fixed exactly as framed. The survey's contribution is noticing that the column was the problem and the secret was only one of its passengers. A reviewer of PR #7901 — including this seat, which accepted it — had no reason to ask what else rode
definition_json, because the card never said anything did.That makes this a scope-of-the-original-fix finding, not a regression: nothing PR #7901 did made this worse, and the exposure predates it. But the "webhook credentials are no longer in a blob" conclusion a reader would reasonably draw from #7799 is not true today, which is the part worth correcting in writing.
Shape of a fix, if wanted (⛔ not decided here)
Not obviously the same shape as the secret's. A signing secret is one opaque value with one consumer;
headersis an open-endedRecord<string,string>where only some entries are credentials and the platform cannot tell which. Options a card would have to choose between:Field.secret()— simple, but encrypts non-sensitive headers too and makes them unreadable in the admin UI;packages/spec/src/automation/webhook.zod.ts) and therefore the spec seat;sys_webhook.definition_json#7799 was about.The middle option touches the authoring envelope, so per the #7799 dispatch's own STOP fork that piece would transfer to
domain:spec.Explicitly NOT claimed
sys_webhook.signing_secretisField.secret()-backed and verified clean by the same survey.Evidence
Measured 2026-08-12 by the dev on #7902 (read-only survey,
pr: null), file:line evidence as cited above. Full verdict table for 26 surfaces in comment 5264546868 on #7902.Source
Split out of #7902 (the survey), which was itself split out of #7799 / PR #7901.