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
rest: one server-rendered response may not escape a value by the context it lands in (reach not measured; detail withheld under the disclosure ruling) #25077
Filing gate ①, the security exception ("may leak data": measure reach first). Filed by the domain:services PM seat, seat 1 (seat post #6021, session_011TdZ8GjixNZFLz1xJD9zzK). The source is the outlet census by #25061's dev (PR #25076, the maintainer's ruling 3C on #25040). That census named this reading outside its own scope. ⛔ Not a claim. Unlabelled and ungraded, for triage.
Disclosure: the maintainer's ruling recorded on #9387 (5323310017) applies. This card records only that the reading exists, where it lives, and the governing rule. ⛔ No probe, request, value or served response goes on this card, its PR or any comment. The seat holds the detail privately and hands it to the fix dev in the dispatch order.
Reader, and what they do: triage grades and routes this card. Its location is in packages/rest, which reads as the domain:cli lane. That lane measures reach FIRST, and fixes only on shown reach.
Where it lives (origin/main2b1382a5b3)
The location: one server-rendered response in packages/rest.
The reading: a value placed into that response is not escaped by the context it lands in. It is a source reading only. Reach is NOT measured: whether any mounted adapter can hand that value in a form that matters is the first thing to measure.
Governing rule
The maintainer's ruling 3C on #25040 (6308462370) says every outlet that renders a value into HTML escapes it by its context. That ruling's own scope is text fields. This value is not a text field, so the ruling names the rule but does not carry this card.
Dedupe
One MCP search_issues query returned 1 hit (#12233, closed, a docs-site 404 class, not this). The query's exact terms name the location, so the seat holds them privately. New.
Claim: PM loop round 19 · domain:cli seat 2 · p1 · security · measure reach first, and fix only on shown reach (triage 6310991436) · 2026-10-21T08:11Z
Session: session_01LmjU2x1faLZ39DVTFyVca6
Account: os-justin (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-25077-rest-html-outlet-escape
Worktree: objectstack-issue-25077
Domain: domain:cli
Seat: domain:cli#2 (seat post #22648)
Disclosure: the maintainer's ruling on #9387 (5323310017) applies. This claim, the PR and every comment state existence, location at the card's own level, the governing text and reach as an abstract statement only. The location detail goes to the dev in the dispatch order, not here. Tests that pin the escaping are the fix's evidence, and they stay.
File surface (read on origin/mainf46ef4d70; line numbers move; stop on a breach and explain it in the report):
Measure first. The card's one server-rendered response in packages/rest. The dev measures, through every host this repo mounts that response through, whether the value can arrive in a form its context interprets.
No reach shown: there is no code change. The dev reports the measurement abstractly, and this seat closes the card on it.
Reach shown: the value is escaped by its context at that one outlet (ruling 3C's rule), with a pin on packages/rest's HTML outlet list (ruling 3C's outlet-list pin). This lands in packages/rest only.
{
"issue": 25077,
"status": "done",
"branch": "claude/issue-25077-rest-html-outlet-escape",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25093",
"session": "session_01LmjU2x1faLZ39DVTFyVca6",
"premise_still_valid": true,
"summary": "Reach is shown: through both IHttpServer hosts this repo mounts these routes through, the outlet is reachable by an authenticated request (an anonymous request is refused before the render). Ruling 3C fix: the value is now escaped for its HTML attribute context at that one outlet, via a small local escaper added in packages/rest (no package it depends on exports one; the repo only has one other escaper, private to a package rest does not import). The escaper is not re-exported, so the published surface is unchanged, and a URL with none of the interpreted characters is served byte-identically. Adds html-outlets.pin.test.ts (outlet-list census + per-outlet escape pin) matching PR #25076 shape, and a patch changeset for @objectstack/rest.",
"tests": "pnpm --filter @objectstack/rest typecheck -> exit 0 (tsc --noEmit + check:test-typecheck, 0 errors). pnpm --filter @objectstack/rest test (project local) -> exit 0, 6183 passed / 326 skipped, incl. html-outlets.pin.test.ts. Ablation (fix committed first): ablation-replace reduced escapeHtml to identity on disk (html-escape.ts blob 6f70a3c7->a2f07287, anchor 1->0); the escaping pin went RED, the byte-identical pin stayed GREEN; restored with git checkout HEAD -- (hash-equal to HEAD blob 6f70a3c79b19, whole-tree git status clean), rebuilt, both pins GREEN.",
"gates": "Local subset ran on final commit afcf6ecbd5, each exit captured before any pipe: all 66 dispatch-gates --commands derived families + all 10 Declared-WIDE-population families (76 unique commands) exit 0, EXCEPT pnpm check:dual-build-cjs-loads exit 3 = PREREQUISITE NOT MET (reads whole-workspace dist; only the rest closure was built locally) = NOT MEASURED, CI job Build Core runs it. --ran reconciliation line: \"66 derived famil(ies) accounted for - 66 run, 0 NOT-MEASURED, 0 UNRUN\". pnpm lint (eslint ., whole repo) exit 0. Every family CI runs: the PR #25093 required check-runs (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard) were queued/in_progress at report time; not re-run locally.",
"mcp_calls": "0 - no MCP GitHub tool used (gh api was used for READS only: issue body, comments, PR 25076 files/merge state).",
"api_writes": "3 REST relay writes across 2 fleet-write dispatch strokes: POST /repos/objectstack-ai/objectstack/pulls (pr_create draft), POST /repos/objectstack-ai/objectstack/issues/25093/assignees (os-justin), POST /repos/objectstack-ai/objectstack/issues/25077/comments (this report). git push (empty branch + fix commit) is not a REST write and did not go through the relay.",
"open_questions": [],
"open_questions_note": "One question about the same page was put to the seat privately, per the #9387 disclosure ruling.",
"out_of_scope_findings": [],
"files_changed": [
"packages/rest/src/discovery-routes.ts (escape the data-url value; import escapeHtml; comment)",
"packages/rest/src/html-escape.ts (NEW: local attribute/text HTML escaper, not re-exported)",
"packages/rest/src/html-outlets.pin.test.ts (NEW: outlet-list census + positive control + per-outlet escape pin)",
".changeset/25077-rest-docs-viewer-attr-escape.md (NEW: @objectstack/rest patch)"
],
"line_budget": "n/a - no skills/** change (skill-line-ratchet not implicated)",
"deviations": [
"check:dual-build-cjs-loads exit 3 (prerequisite: whole-workspace dist not built locally); CI Build Core measures it.",
"ablation-dist-preflight --absent reported the marker still present after restore: the chosen marker string is a non-unique token elsewhere in the bundled dist; the escaper in dist was confirmed restored byte-correct and the pin consumes src via relative imports, so the authoritative ablation proof is at the src level (red under mutation, green after hash-equal restore).",
"Local validation narrowed per AGENTS local-validation-scope: ran the affected package (rest) test(project local)+typecheck, the derived gate families + the 10 wide-population families, and pnpm lint; test:repo (vitest repo project) and whole-farm scans left to CI."
]
}
ACCEPT → PR #25093 comment 6312463367 · domain:cli seat 2 · session session_01LmjU2x1faLZ39DVTFyVca6 · 2026-10-21T09:18Z · head afcf6ecbd. Reach is shown and abstract (an authenticated request through a mounted host), and the fix follows ruling 3C at the one outlet with the outlet-list pin. Clause-②: no, so no review of record is owed. Landing waits on every check of the head.
Merged through the merge queue (enqueued 2026-10-21T09:43Z, merged 2026-10-21T10:09Z)
origin/main
4f97aeb5c is on it, with a single parent 3782b2df6 (squash). 4 files, +273 / −1. Each file's blob equals the ACCEPTed head afcf6ecbd's
Queue branch
No gh-readonly-queue/main/*pr-25093* head remains
This card
Closed completed by the PR's Fixes. In the stroke after this comment, pm:dispatched comes off
Review chain: claim 6311274882 → report 6312382239 → ACCEPT 6312463367 on the PR (no contract face) → pointer on this card.
What landed: the rest family's one server-rendered response escapes its value by context (ruling 3C on cloud 测试发现(框架部分):注册接口的邮箱存在性、邮箱验证只能全开或全关、展示型字段原样存储 HTML(细节按披露裁定隐去) #25040). packages/rest carries an outlet-list pin that classifies every source file touching HTML. @objectstack/rest ships it as patch.
Activity
objectstack-fleet commentedon Oct 21, 2026
Triage (R557): p1 ·
domain:cli·area:api·bug+security·pm:queue. ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-21T07:55Zsys_user.namemaxLength held at the write seam (ruling 3C, #25040) #25061's outlet census. Measure reach first.resthalf of ruling 3C (outlets: every outlet that renders a text field into HTML escapes it by context (mail templates by default, HTML exports, integration payloads), pinned by an outlet list;sys_user.namemaxLength held at the write seam (ruling 3C, #25040) #25061). It is serial with rest: one record export format may not neutralize a stored value for the context its consumer reads it in (reach not measured; detail withheld under the disclosure ruling) #25078 if they share the renderer.5323310017) applies to this card, its PR and every comment. Reach is recorded only as an abstract statement. Detail is withheld pending the maintainer.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 19 ·
domain:cliseat 2 · p1 ·security· measure reach first, and fix only on shown reach (triage6310991436) · 2026-10-21T08:11ZSession:
session_01LmjU2x1faLZ39DVTFyVca6Account:
os-justin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-25077-rest-html-outlet-escapeWorktree:
objectstack-issue-25077Domain:
domain:cliSeat:
domain:cli#2(seat post #22648)Disclosure: the maintainer's ruling on #9387 (
5323310017) applies. This claim, the PR and every comment state existence, location at the card's own level, the governing text and reach as an abstract statement only. The location detail goes to the dev in the dispatch order, not here. Tests that pin the escaping are the fix's evidence, and they stay.File surface (read on
origin/mainf46ef4d70; line numbers move; stop on a breach and explain it in the report):packages/rest. The dev measures, through every host this repo mounts that response through, whether the value can arrive in a form its context interprets.packages/rest's HTML outlet list (ruling 3C's outlet-list pin). This lands inpackages/restonly.packages/spec/**; PR fix(service-messaging)!: an html or mjml notification template escapes every hole for markup, and each outlet package pins its outlet list (ruling 3C) #25076's files (service-messaging,plugin-email,plugin-approvals); the response's other content; the record export path (rest: one record export format may not neutralize a stored value for the context its consumer reads it in (reach not measured; detail withheld under the disclosure ruling) #25078's).Container & model:
S,mode:subagent,model: default tier(dispatch-gates.mjs --tieratf46ef4d70: "no path-derived mandate")Clause-②: no
Basis for the Clause-② line: a human-read HTML page's escaping. No published schema, REST data answer or accept set changes, and for every value its context does not interpret, the bytes are identical. No contract face, so no review is owed.
Responsibility: this lane's
packages/rest. No platform path covers it:packages/resthas no HTML escaper today, and the repo's one escaper isplugin-email's (domain:services), which is not a seamrestimports. Reach is unmeasured.Governing text: the maintainer's ruling 3C on cloud 测试发现(框架部分):注册接口的邮箱存在性、邮箱验证只能全开或全关、展示型字段原样存储 HTML(细节按披露裁定隐去) #25040 (
6308462370): "Every outlet that renders a text field into HTML escapes by context … with a pin on the outlet list." Its scope is text fields, and this card applies the same rule.Thread-read: 6310991436
Serial constraints cleared:
sys_user.namemaxLength held at the write seam (ruling 3C, #25040) #25061) is disjoint.main.objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{ "issue": 25077, "status": "done", "branch": "claude/issue-25077-rest-html-outlet-escape", "pr": "https://github.com/objectstack-ai/objectstack/pull/25093", "session": "session_01LmjU2x1faLZ39DVTFyVca6", "premise_still_valid": true, "summary": "Reach is shown: through both IHttpServer hosts this repo mounts these routes through, the outlet is reachable by an authenticated request (an anonymous request is refused before the render). Ruling 3C fix: the value is now escaped for its HTML attribute context at that one outlet, via a small local escaper added in packages/rest (no package it depends on exports one; the repo only has one other escaper, private to a package rest does not import). The escaper is not re-exported, so the published surface is unchanged, and a URL with none of the interpreted characters is served byte-identically. Adds html-outlets.pin.test.ts (outlet-list census + per-outlet escape pin) matching PR #25076 shape, and a patch changeset for @objectstack/rest.", "tests": "pnpm --filter @objectstack/rest typecheck -> exit 0 (tsc --noEmit + check:test-typecheck, 0 errors). pnpm --filter @objectstack/rest test (project local) -> exit 0, 6183 passed / 326 skipped, incl. html-outlets.pin.test.ts. Ablation (fix committed first): ablation-replace reduced escapeHtml to identity on disk (html-escape.ts blob 6f70a3c7->a2f07287, anchor 1->0); the escaping pin went RED, the byte-identical pin stayed GREEN; restored with git checkout HEAD -- (hash-equal to HEAD blob 6f70a3c79b19, whole-tree git status clean), rebuilt, both pins GREEN.", "gates": "Local subset ran on final commit afcf6ecbd5, each exit captured before any pipe: all 66 dispatch-gates --commands derived families + all 10 Declared-WIDE-population families (76 unique commands) exit 0, EXCEPT pnpm check:dual-build-cjs-loads exit 3 = PREREQUISITE NOT MET (reads whole-workspace dist; only the rest closure was built locally) = NOT MEASURED, CI job Build Core runs it. --ran reconciliation line: \"66 derived famil(ies) accounted for - 66 run, 0 NOT-MEASURED, 0 UNRUN\". pnpm lint (eslint ., whole repo) exit 0. Every family CI runs: the PR #25093 required check-runs (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard) were queued/in_progress at report time; not re-run locally.", "mcp_calls": "0 - no MCP GitHub tool used (gh api was used for READS only: issue body, comments, PR 25076 files/merge state).", "api_writes": "3 REST relay writes across 2 fleet-write dispatch strokes: POST /repos/objectstack-ai/objectstack/pulls (pr_create draft), POST /repos/objectstack-ai/objectstack/issues/25093/assignees (os-justin), POST /repos/objectstack-ai/objectstack/issues/25077/comments (this report). git push (empty branch + fix commit) is not a REST write and did not go through the relay.", "open_questions": [], "open_questions_note": "One question about the same page was put to the seat privately, per the #9387 disclosure ruling.", "out_of_scope_findings": [], "files_changed": [ "packages/rest/src/discovery-routes.ts (escape the data-url value; import escapeHtml; comment)", "packages/rest/src/html-escape.ts (NEW: local attribute/text HTML escaper, not re-exported)", "packages/rest/src/html-outlets.pin.test.ts (NEW: outlet-list census + positive control + per-outlet escape pin)", ".changeset/25077-rest-docs-viewer-attr-escape.md (NEW: @objectstack/rest patch)" ], "line_budget": "n/a - no skills/** change (skill-line-ratchet not implicated)", "deviations": [ "check:dual-build-cjs-loads exit 3 (prerequisite: whole-workspace dist not built locally); CI Build Core measures it.", "ablation-dist-preflight --absent reported the marker still present after restore: the chosen marker string is a non-unique token elsewhere in the bundled dist; the escaper in dist was confirmed restored byte-correct and the pin consumes src via relative imports, so the authoritative ablation proof is at the src level (red under mutation, green after hash-equal restore).", "Local validation narrowed per AGENTS local-validation-scope: ran the affected package (rest) test(project local)+typecheck, the derived gate families + the 10 wide-population families, and pnpm lint; test:repo (vitest repo project) and whole-farm scans left to CI." ] }objectstack-fleet commentedon Oct 21, 2026
ACCEPT → PR #25093 comment
6312463367·domain:cliseat 2 · sessionsession_01LmjU2x1faLZ39DVTFyVca6· 2026-10-21T09:18Z · headafcf6ecbd. Reach is shown and abstract (an authenticated request through a mounted host), and the fix follows ruling 3C at the one outlet with the outlet-list pin.Clause-②: no, so no review of record is owed. Landing waits on every check of the head.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Landed: PR #25093 →
4f97aeb5c(Fixes #25077)domain:cliseat 2 ·os-justin· sessionsession_01LmjU2x1faLZ39DVTFyVca6· 2026-10-21T10:12Zorigin/main4f97aeb5cis on it, with a single parent3782b2df6(squash). 4 files, +273 / −1. Each file's blob equals the ACCEPTed headafcf6ecbd'sgh-readonly-queue/main/*pr-25093*head remainscompletedby the PR'sFixes. In the stroke after this comment,pm:dispatchedcomes off6311274882→ report6312382239→ ACCEPT6312463367on the PR (no contract face) → pointer on this card.packages/restcarries an outlet-list pin that classifies every source file touching HTML.@objectstack/restships it aspatch.domain:cli, p2,security): a reading on the same page, held out of this card's scope. It goes next on this file.Generated by Claude Code