Repository navigation
security(sharing): the share-link resolve gate may answer an email-audience refusal at a cost that depends on caller input — detail withheld pending maintainer #23474
Copy link
Copy link
Closed
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:servicespriority:p2Medium: important, M3Medium: important, M3security
Description
Filing gate ①, a product defect at a public door, under the "may leak data" exception, so the first point is to measure reachability first. Filed by domain:services seat 1 (#6021) · marchtian · session session_01LxHPTb1LxL7a6WkQbALKnD, from flag 6 of the contract-review record 6167789839 on PR #23471 (#23372). ⛔ Not a claim; grade and lane are triage's.
- Class: a possible side channel at the share-link resolve door for an
email-audience link. plugin-sharing: a share link with a password and an email audience answers an anonymous caller who sends the correct password with the wrong-password code; reordering needs a decision (allowlist-membership oracle) #23372 / PR fix(plugin-sharing, runtime): an email-audience share link answers every caller it does not open for with one refusal #23471 makes every refusal of such a link byte-identical, pinned on both mounts. Bytes are not the only thing a caller can read, though. The reviewer read from the code (not measured) that the service-side refusal may cost different amounts depending on the caller's input, and that could re-open the allowlist-membership question plugin-sharing: a share link with a password and an email audience answers an anonymous caller who sends the correct password with the wrong-password code; reordering needs a decision (allowlist-membership oracle) #23372 closed for the answer bytes. Under the lane's disclosure rule, the mechanism, the inputs and any measurement are withheld pending maintainer, held in Claude Code sessionsession_01LxHPTb1LxL7a6WkQbALKnD. The precedent is security(sharing): share-link password handling falls short of the platform's credential rules (response shape, hashing strength, transport) — detail withheld pending maintainer #21839. - reach: not yet measured. The first step is a measurement, on both mounts, of whether the difference is observable over the network at all. If it is not, the card closes
not_plannedwith the reading. - Direction (if it is reachable): the refusal of an
email-audience link costs the same whatever the caller sends, as its bytes already do. The fix lands inplugin-sharing'sresolveToken, which both mounts share. ⛔ No change to the answer bytes PR fix(plugin-sharing, runtime): an email-audience share link answers every caller it does not open for with one refusal #23471 pins. - Reader who acts: triage grades and routes it, likely
domain:services(plugin-sharing). - Dedupe (all states): semantic search "share link password timing" → 4 hits (plugin-sharing: a share link with a password and an email audience answers an anonymous caller who sends the correct password with the wrong-password code; reordering needs a decision (allowlist-membership oracle) #23372 open, plugin-sharing: a share link with both a password and audience 'signed_in', opened anonymously with the CORRECT password, is answered 401 WRONG_PASSWORD, so the landing page says 'Wrong password.' and never offers sign-in #23328 closed, security(sharing): share-link password handling falls short of the platform's credential rules (response shape, hashing strength, transport) — detail withheld pending maintainer #21839 closed, sharing: the X-Share-Password header declares no encoding, so a share-link password with a character above U+00FF cannot be sent from a browser — while ShareDialog and createLink mint such passwords #22049 closed), and "plugin-sharing share link email audience password wrong-password code" → 5 hits (adds plugin-sharing: the two mounts of the share-link messages door disagree for a signed-in caller of a signed_in conversation link — the plugin mount answers 404, the dispatcher 200 — because the plugin mount passes no signed-in user to resolveToken #23371 open). None covers this. security(sharing): share-link password handling falls short of the platform's credential rules (response shape, hashing strength, transport) — detail withheld pending maintainer #21839 covered how the password is returned, stored and transmitted, not the cost of the audience refusal.
Related: #23372 · PR #23471 · #21839.
Generated by Claude Code
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:servicespriority:p2Medium: important, M3Medium: important, M3security
Activity
objectstack-fleet commentedon Oct 14, 2026
Triage: first grade ·
security·priority:p2·domain:services·area:access·pm:queue· measure reach first · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-14T12:53ZRead fresh. The mechanism is withheld under the lane's disclosure rule (held in session
session_01LxHPTb1LxL7a6WkQbALKnD, precedent #21839). Triage grades on what the card states.priority:p2: a possible side channel on theemail-audience refusal, read from code and not measured.securitydoes not raise the grade by itself (〈定级判据〉: the reach sets the ceiling).not_plannedwith the reading, as it says.domain:services(plugin-sharingresolveToken, which both mounts share).findingleaves on grading.objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 2 · 2026-10-14T13:27Z · maintainer direct dispatch: the maintainer, in session
session_01LxHPTb1LxL7a6WkQbALKnD, verbatim 「23474 你负责派发。」 (opening marker6169433344on #6021)Session:
session_01LxHPTb1LxL7a6WkQbALKnDAccount:
marchtian(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-23474-email-refusal-costWorktree:
objectstack-issue-23474Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface:
packages/plugins/plugin-sharing/src/share-link-service.ts(resolveToken), possiblypackages/plugins/plugin-sharing/src/share-link-password.ts, their tests underpackages/plugins/plugin-sharing/src/, and one.changeset/23474-*.mdif a fix lands. The measurement harness is scratch and is not committed (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default — dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate … The tier stays the PM's per-card judgment call"Clause-②: no
Responsibility:
plugin-sharing's resolveToken, which both resolve mounts share (mechanism withheld pending maintainer, held in this session) | none — PR #23471 equalised the answer bytes, not the cost | anyone holding a token for an email-audience link, through the API or SDK; reachability over the network is unmeasured, and this claim's first step measures itThread-read: 6168800480
Serial constraints cleared: no open PR touches
share-link-service.tsorshare-link-password.ts(12 open PRs read at this claim), and neither file changed onmaintoday. #23371 (pm:queue) is onshare-link-routes.ts, which is disjoint. PR #23471's answer bytes are pinned inruntime/src/domains/share-links-refusal-order.test.ts, and this card keeps them byte-identical (triage6168800480).Measurement first. ⛔ No fix is written before the reachability reading exists. If the difference is not observable, nothing is changed and the card closes
not_plannedwith the reading. Disclosure: ⛔ no mechanism, inputs or reproduction recipe on any public surface. Evidence is described abstractly.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{
"issue": 23474,
"status": "done",
"branch": "claude/issue-23474-email-refusal-cost",
"pr": "#23506",
"session": "session_01LxHPTb1LxL7a6WkQbALKnD — subagent of the PM session (same id); identity is the branch",
"premise_still_valid": true,
"summary": "Reach measured first, on both resolve mounts: the plugin route on a real booted server over HTTP and the dispatcher twin in-process through its production handler. The difference was observable on both and cleared the dispatch's decision rule, so the fix landed. An email-audience share link's refusal in plugin-sharing's resolveToken, shared by both mounts, now does the same work whatever email and password are presented. Answer bytes unchanged: the PR #23471 refusal-order table passes untouched. The same measurement after the fix reads within noise on both mounts. Full readings held by the seat (disclosure rule).",
"tests": "At 0088cf1. New pin share-link-service.email-refusal-cost.pin.test.ts counts the refusal's work per request, off-list vs allowlisted email, with a wrong password and with none. It was red on the pre-fix resolveToken (2 of 3 failed, the serve control passed) and is green after. Ablation via scripts/ablation-replace.mjs restores the pre-fix ordering: red on unequal counts, then restored, blob equal to HEAD, git diff HEAD empty. plugin-sharing test 46 files / 1061 passed; typecheck exit 0, new pin in the test-layer program; runtime share-links-refusal-order.test.ts 60/60 against a rebuilt plugin-sharing dist. Before/after measurement grids held by the seat.",
"gates": "Local, at 0088cf1: the 65 families dispatch-gates --commands derives, run under os-verify-lock in 4 chunks; every one exit 0, each code captured before any pipe. dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, all codes recorded). CI on PR 23506 head 0088cf1 when this report was written: Check PR Size success, Check Documentation Links success, the claim/closing guards success (No other open PR may claim the same issue / same single-writer path, The card this PR closes must claim this branch, Part-of PR must not also close its card), filter success. Build Core, Test Core 1-6, all four Type Check lanes, the six Lint lanes, Check Changeset and Governed Surface Queue Guard in_progress. Path-scheduled jobs skipped (Console Pin Gate, Dogfood shards, Temporal Conformance, Build Docs, Dogfood Verify CLI, Packed-tarball smoke).",
"mcp_calls": "0",
"api_writes": "4 REST writes, all through the fleet-write relay as objectstack-fleet[bot]. Two dispatch strokes: POST /repos/objectstack-ai/objectstack/dispatches, once for pr_create and once for this comment. pr_create executed POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/23506/assignees. This comment: POST /repos//issues/23474/comments. Plus 2 git pushes (not REST): the first push, then one force-with-lease rewrite of this branch before the PR existed. Labels: zero writes; the dispatch named none and skip-changeset does not apply because a patch changeset ships.",
"files_changed": [
".changeset/23474-email-refusal-cost.md +11 -0",
"packages/plugins/plugin-sharing/src/share-link-service.email-refusal-cost.pin.test.ts +154 -0",
"packages/plugins/plugin-sharing/src/share-link-service.ts +31 -10"
],
"line_budget": "n/a — no skills/** or governed surface touched",
"deviations": [
"Empty-branch push before edits (agent-file rule 1) was refused by the permission classifier [Git Destructive]; not retried on another route. The dispatch also says push nothing before the fix, and the agent file says push the empty branch first; the two conflict.",
"Branch history rewritten once with --force-with-lease=BRANCH:42e901a8 (all five AGENTS.md multi-agent section 3 conditions held, no PR existed) to make the commit message, changeset and code comments abstract per the disclosure clause. The superseded head 42e901a sat on the remote branch before the rewrite. Detail held by the seat.",
"The platform auto-backgrounded the derived gate run twice at the 10-minute foreground ceiling; both were collected in the foreground (tail --pid and a results-file read), all exit 0.",
"origin/main not merged before opening (declared narrowing): the branch is based at 8731510, and the 4 commits main gained since touch none of the PR's paths. The queue rebuilds onto current main.",
"The ablation run's output is described in the PR body rather than quoted verbatim (disclosure); the verbatim quote is held by the seat.",
"check:* gates ran under os-verify-lock per the lane clause, although the agent file's resource rule exempts them from the lock."
],
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
ACCEPT → PR #23506 @
0088cf196f3144a3f1b89b53f015a62d7e10498bdomain:servicesseat 1 ·marchtian·session_01LxHPTb1LxL7a6WkQbALKnD· 2026-10-14T14:57Z, under claim6169464027. The review was against GitHub andorigin/main, ⛔ not against the report (6171241065). Disclosure kept: the mechanism and the readings stay with the seat.Reach: measured first, on both resolve mounts, and observable on both; after the fix it is within the noise control on both. The decision rule in the dispatch was cleared by a wide margin. The readings are held by the seat and can be given to the maintainer on request.
Checklist
main. The body's first line isFixes #23474, with no other closing keyword beside an open card number.Clause-②: nois at the start of a line.share-link-service.ts, a new pin test inplugin-sharing/src/, and.changeset/23474-email-refusal-cost.md.share-link-password.tsis untouched.@objectstack/plugin-sharing: patch, a published package. Both of its sentences hold against the diff: the refusal's work no longer depends on the caller's input, and the answer bytes are unchanged.resolveToken, only theemailaudience's verdict moves. It is computed, the existing password step runs as before, and the two verdicts are combined into the samenull. Non-emailaudiences keep their exact flow,signed_in's early return included. The legacy-hash upgrade still requires both verdicts. No new code path serves a request that was refused before, and no served request is refused now.resolveToken(2 of 3 failed). The ablation that puts the old ordering back turns them red again, and the restore is proven.packages/spec, no governed path, noClause-②: yes). Percontract-review.mdthe review of record is CI plus this seat's read. ⛔ No second agent.--force-with-lease, before any PR existed, to make the commit message and changeset abstract. The AGENTS.md multi-agent conditions were met.check:*ran under the verify lock per the lane clause.email+password link's refusal now pays the password verify for any caller who presents a password, whatever the email. A token holder could already trigger that verify on any password-protected link, so this adds no new reach.Landing (pending; the seat holds it): every check green on
0088cf19(it was converging at this read), thenpr_readyandautomerge_enablethrough the relay. The path face is not governed, and the diff is 206 lines.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
PR #23506 queue kick: infrastructure, re-queued once ·
domain:servicesseat 1 ·session_01LxHPTb1LxL7a6WkQbALKnD· 2026-10-14T15:52ZMachine input: merge-queue-triage
6172399177on PR #23506, queue run38589026391, classificationfailure, no aggregatable test-file signature, this PR's first queue failure.Build Core, stepUpload build artifacts, first errorFailed to CreateArtifact: Unable to make request: ETIMEDOUT. The step had already prepared 1767 files when the artifact service request timed out.share-link-service.tsand a pin test).Build Corewas green on the 8 queue runs before this one and on the 2 latestmainpushes. In this same queue run all sixTest Coreshards passed on the merged tree.ETIMEDOUT), not an assertion.automerge_enablethrough the relay) on head0088cf19, unchanged. ⛔ A second failure is treated as real and is not re-queued.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Landed: PR #23506 →
d1e28411onmaindomain:servicesseat 1 ·marchtian·session_01LxHPTb1LxL7a6WkQbALKnD· 2026-10-14T16:20Z, under claim6169464027and ACCEPT6171312135.merged: trueat 2026-10-14T16:19:10Z, merge commitd1e284113759db34b9dabcd18b9c9da22b32cde96172432123), re-queued once at 15:53, mergedorigin/maind1e28411is on main. The fix inshare-link-service.ts, the pinshare-link-service.email-refusal-cost.pin.test.tsand.changeset/23474-email-refusal-cost.mdare all on main.domain:servicesopen cards: 21, the same set as before this card. Only #23474 closed.State change in this act: the card was closed
completedby the merge.pm:dispatchedand the assignee are now cleared; the domain, type and priority labels stay. The mechanism and the readings stay with the seat, under the lane's disclosure rule.Generated by Claude Code