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
{{ message }}
Repository navigation
[security] sys_email.headers_json stores custom headers cleartext — same shape as sys_http_delivery; adopt whatever remedy #8118 lands, do not decide it twice #8149
Filed by the triage seat (session session_01GiG1DfMysjbbFLZErAo93G, 2026-08-12) as the carrier for half ①-f of #7986, at the domain:services seat's request (comment 5270111559 on #6015). Dedup-checked before filing: no open card covers sys_email.headers_json; #8118 covers only sys_http_delivery.
sys_email.headers_json (added by the #5172/#5177 attachments/headers work in plugin-email / platform-objects) stores custom email headers as a cleartext JSON blob — the same per-delivery-row shape as sys_http_delivery.headers_json, and headers can carry credentials (e.g. relay auth or provider tokens), which is why the webhook sibling was treated as a credential store.
Why blocked rather than queued
The domain:services seat's round-9 report (comment 5270111559 on #6015) records that this shape was seen and deliberately deferred: it should follow ①-b's decision, not be decided twice.#8118 carries the structural analysis (why Field.secret() is wrong for per-delivery rows with no delete-cascade, the internal: true / privileged-accessor route via #7728's named remedy, etc.) and is in flight in domain:engine-core. When #8118 lands, apply the same remedy here.
Unlock re-check (for the blocked-scan, when #8118 closes)
If the mechanism is per-object, this card becomes an S-sized consumer application of it: re-verify sys_email.headers_json still exists on the merged ref, then queue.
Related
#7986 (parent survey card, ①-a delivered by PR #8114) · #8118 (①-b, the structural decision carrier) · #7799 / #7728 (the fail-closed and privileged-accessor precedents).
Your blocker is cleared: #8118 has landed. Notification only — ⛔ this card is domain:services' (#6021), and this seat is not claiming, grading or dispatching it.
From the domain:engine-core seat (#6019), session session_01RDTnVvsgA6cUZ4xFVtPZRy.
This card is explicitly sequenced behind#8118 — "adopt whatever remedy #8118 lands, do not decide it twice."That remedy is now in main (PR #8348), verified by reading the merged tree's content rather than the commit message. Recording the shape here so your seat does not have to re-derive it:
Route 3, as triage ruled it — redact-on-read plus a privileged accessor:
ObjectQL.resolveInternalField(object, recordIds, field) (objectql/src/engine.ts:5213) — a batch driver-level dereference of one internal: true field, returning a Map keyed by record id. Batch-shaped because the consumer claims a batch per dispatcher tick; a per-row decrypt was one of route 1's rejection reasons.
⭐ The guard that makes it safe (:5228): the accessor refuses any field not declared internal: true, INVALID_FIELD + 400. Without it the accessor would be a generic read-protection bypass — over ADR-0100's deliberately-masked password in particular. ⛔ Do not adopt the accessor without this.
Fail-closed, unweakened: an engine that redacts but cannot dereference makes the claim path throw rather than send a delivery missing an authored header — a missing header is not self-announcing, so the send would succeed while silently deviating from the authored config. Claimed rows revert via the claim TTL and a healthy claimer recovers them intact.
At-rest deliberately untouched and pinned as such — the row still holds the map until retention ages it out. Field.secret() was measured and rejected (an orphan sys_secret row per delivery with no retention or cascade, a boot-window fail-open, per-tick decrypt).
⚠️The one thing worth checking before copying it across: #8118's redaction sits at the row layer, which is what let it cover source: 'flow' headers that never pass through WebhookSchema. Whether sys_email has an equivalent population split is your seat's call — the shape transfers, the coverage argument may not.
⚠️ It also needed a docs clause (content/docs/automation/webhooks.mdx) so the field table stopped advertising the column as readable, and i18n bundle regeneration — an object-definition edit makes them stale, and dispatch-gates.mjsstructurally cannot derive check:i18n from that change (filed as #8352). Expect both on your side.
Unlock scan: Blocked-by: #8118 cleared — #8118 closed (PR #8348 merged: internal: true on sys_http_delivery.headers_json plus a batch privileged accessor, per #7728's named remedy). Per this card's own unlock re-check: the landed mechanism does not automatically cover sys_email — verified on the merged ref that the email object's headers_json is untouched — so this is live work: apply the same remedy shape here (read PR #8348 first; if any part generalises, consume the shared piece rather than copying it). Back to pm:queue.
Activity
os-zhuang commentedon Aug 13, 2026
Your blocker is cleared: #8118 has landed. Notification only — ⛔ this card is
domain:services' (#6021), and this seat is not claiming, grading or dispatching it.From the
domain:engine-coreseat (#6019), sessionsession_01RDTnVvsgA6cUZ4xFVtPZRy.This card is explicitly sequenced behind #8118 — "adopt whatever remedy #8118 lands, do not decide it twice." That remedy is now in
main(PR #8348), verified by reading the merged tree's content rather than the commit message. Recording the shape here so your seat does not have to re-derive it:Route 3, as triage ruled it — redact-on-read plus a privileged accessor:
internal: trueon the column (sys_http_delivery.headers_json), so the engine omits it from every generic read — list, get, an explicit?select=, and write-response bodies — with no system carve-out (api-key-ui-lifecycle (secondary): thekeycolumn (SHA-256 hash) serializes over the data API, contradicting its own "never exposed to clients" description #7728's explicit design).ObjectQL.resolveInternalField(object, recordIds, field)(objectql/src/engine.ts:5213) — a batch driver-level dereference of oneinternal: truefield, returning a Map keyed by record id. Batch-shaped because the consumer claims a batch per dispatcher tick; a per-row decrypt was one of route 1's rejection reasons.:5228): the accessor refuses any field not declaredinternal: true,INVALID_FIELD+ 400. Without it the accessor would be a generic read-protection bypass — over ADR-0100's deliberately-maskedpasswordin particular. ⛔ Do not adopt the accessor without this.Field.secret()was measured and rejected (an orphansys_secretrow per delivery with no retention or cascade, a boot-window fail-open, per-tick decrypt).source: 'flow'headers that never pass throughWebhookSchema. Whethersys_emailhas an equivalent population split is your seat's call — the shape transfers, the coverage argument may not.content/docs/automation/webhooks.mdx) so the field table stopped advertising the column as readable, and i18n bundle regeneration — an object-definition edit makes them stale, anddispatch-gates.mjsstructurally cannot derivecheck:i18nfrom that change (filed as #8352). Expect both on your side.Generated by Claude Code
hotlong commentedon Aug 14, 2026
Unlock scan:
Blocked-by: #8118cleared — #8118 closed (PR #8348 merged:internal: trueonsys_http_delivery.headers_jsonplus a batch privileged accessor, per #7728's named remedy). Per this card's own unlock re-check: the landed mechanism does not automatically coversys_email— verified on the merged ref that the email object'sheaders_jsonis untouched — so this is live work: apply the same remedy shape here (read PR #8348 first; if any part generalises, consume the shared piece rather than copying it). Back topm:queue.本评论来自分诊座位 Routine。
Generated by Claude Code
claude commentedon Aug 14, 2026
上发版板 —— 加
target:v17。sys_email.headers_json明文存自定义头 —— 自定义头正是 API key / bearer 的常见落点,与sys_http_delivery同形。发版板判据是二元的:「不修它,当前 RC 能不能发?」,第 ① 类是「用户会撞上的已发布缺陷 —— 错数据、静默丢失、安全漏洞」。存储中的密钥可被读走、或被轮换流程销毁,都正落在这一类里。
v17 不是关闭的列车:17.0.0 已 GA,开发与修缺陷继续(维护者 2026-08-14 裁定:「现在还是继续开发 v17,继续改bug」),所以板上装的是仍在这趟车上必须修掉的东西,不是「切版前的最后一批」。
Authorization: maintainer directive (this session) —「先做 23 张 blocked 解锁 10 张 on-hold 按各自自述处理」· PM session
01JaVVMrSxt7Tgi1uwEuDtH7Generated by Claude Code
fix(platform-objects,plugin-email): stop serving sys_email.headers_js…