Uh oh!
There was an error while loading. Please reload this page.
fix(service-messaging,objectql): stop serving sys_http_delivery.headers_json on the generic read path — internal: true + a batch privileged accessor (#8118) - #8348
Conversation
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 16 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also reference the affected code. These are read-only:
|
…eaders_json as a readable column (#8118) The page's own rule — 'reading a delivery row tells you what was sent, not how to forge it' — was false while headers_json served Authorization headers verbatim over the data API, and this PR is what makes it true. Mark the row internal in the field table and state that the guarantee now covers the authored headers, recovered only by the dispatcher's privileged read at claim time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy
…n description (#8118) The internal:true marking on sys_http_delivery.headers_json added a field description; an object-definition change regenerates the package's i18n bundles, and the four service-messaging bundles were not regenerated with it (check:i18n DRIFTED (4)). Merge-mode regeneration — the new help key arrives filled with source text in all four locales, no existing translation overwritten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy
Uh oh!
There was an error while loading. Please reload this page.
Fixes#8118
Route 3, as ruled by triage (issue comment 5269775253, not reopenable): redact-on-read via
internal: trueonsys_http_delivery.headers_json, plus a purpose-built privileged accessor inpackages/objectql— theresolveSecretpattern that #7728 itself names as the remedy.What this does
packages/objectql—ObjectQL.resolveInternalField(object, recordIds, field): a batch, driver-level dereference of ONEinternal: truefield, returning a Map keyed by record id (null= row exists with nothing stored; absent = no such row). Refuses any field not declaredinternal: truewith the ADR-0112 envelope (INVALID_FIELD, status 400) — without that guard it would be a generic read-protection bypass (over ADR-0100's deliberately-maskedpasswordin particular). Batch-shaped because its consumer claims a batch per dispatcher tick: one driver read per claim batch, never per row (the per-row cost was one of route 1's rejection reasons).packages/services/service-messaging—sys_http_delivery.headers_jsonis declaredinternal: true, so the engine omits it from every generic read (list, get, explicit?select=headers_json) 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). The redaction sits at the ROW layer, so it covers the whole delivery population:source: 'webhook'rows (WebhookSchema-authored headers) ANDsource: 'flow'rows (per-run interpolated headers that never pass through WebhookSchema — the half every author-declared shape forgets).SqlHttpOutbox.claim()— the one reader that must put the map on the wire verbatim — recovers it through the accessor;list()/redeliver()return the redacted view.claim()throw — nothing goes out missing an authored header (missing headers are not self-announcing; the delivery would succeed while silently deviating from the authored config). The claimed rows revert topendingvia the claim TTL, and a healthy claimer recovers them headers-intact (pinned in test).content/docs/automation/webhooks.mdx— thesys_http_deliveryfield table stops advertisingheaders_jsonas a plain readable column, and the page's own rule ("reading a delivery row tells you what was sent, not how to forge it") now states explicitly that it covers the authored headers — this PR is what makes that sentence true. One page; no release-owned docs touched.Constraint evidence (all four binding constraints)
source: 'flow'shown covered: a dedicated integration case drives the exact input shapehttp-nodes.tsproduces (source: 'flow',refId= node id, per-run interpolated headers) end-to-end through the production enqueue → claim → send path: redacted on the data API, run-scoped bearer verbatim on the wire.sys_webhook.definition_json#7799 regression: the signing pin verifies the way a receiver verifies — recomputed HMAC over the actual wire body equals theX-Objectstack-Signatureheader, byte-identical across redelivery.sys_webhook.headers_secretnor theheadersPatchcompare-before-write; the enqueuer path is unchanged.sys_http_deliveryrow, while it still readsactive:true#8022 regression: no change to enqueue/re-arm or the error ledger; the redaction is read-side only.Field.secret()was measured and rejected on the card (orphansys_secretrow per delivery, boot-window fail-open, per-tick decrypt). A future card that changes the at-rest story flips that pin deliberately.Provenance — resumed work
Four commits (
c17e8677…d232665) were implemented by the previous dev on this card before an account-quota kill in the gate tail; this session inherited the branch, mergedorigin/main(89 commits, including #8327's rewrite of the projection filter inengine.ts— merged clean; the refusal is dotted-leg only, so the accessor's driver-level read and the?select=served-without-it behavior are unaffected), and re-ran all verification from scratch. Full inherited-vs-added accounting is in the dev report on #8118.Out of scope, deliberately
sys_email.headers_json([security] webhook customheadersare still cleartext in two JSON blobs — the sibling of #7799 that PR #7901 did not close #7986 ①-f, card [security]sys_email.headers_jsonstores custom headers cleartext — same shape assys_http_delivery; adopt whatever remedy #8118 lands, do not decide it twice #8149) is not addressed here — it is sequenced behind this card and adopts what lands here. [security] webhook customheadersare still cleartext in two JSON blobs — the sibling of #7799 that PR #7901 did not close #7986 remains open.sys_secretretention (sys_secret: rotations that happened BEFORE #8030's fix left decryptable orphans on deployed instances, and nothing reaps them #8103) is untouched.Verification
pnpm --filter @objectstack/objectql test— 196 files / 3484 tests green (includes the new accessor pins).pnpm --filter @objectstack/service-messaging test— 19 files / 211 tests green (includes the 5-case at-rest integration suite).pnpm --filtertypecheck green on both packages; ESLint clean on all six changed source files.internal: trueturns exactly the 4 redaction/fail-closed cases red and the header-less-delivery case stays green; restored byte-identical from the commit.check-objectui-pin-freshreports the repo-wide pin staleness equally onmain— pre-existing release-lane state, zero lines of this diff.pnpm --filter @objectstack/lint run check:doc-formula-expressions(first run hit the documented unbuilt-dist prerequisiteERR_MODULE_NOT_FOUNDon@objectstack/formula; green after building that closure — a prerequisite, not a finding).Generated by Claude Code