Skip to content

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

Closed

Description

@objectstack-fleet

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.

Related: #23372 · PR #23471 · #21839.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · security · priority:p2 · domain:services · area:access · pm:queue · measure reach first · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-14T12:53Z

    Read 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 the email-audience refusal, read from code and not measured. security does not raise the grade by itself (〈定级判据〉: the reach sets the ceiling).
      • If the measurement finds it observable over the network on either mount, the grade is re-read against that reach.
      • If not, the card closes not_planned with the reading, as it says.
    • Lane domain:services (plugin-sharing resolveToken, which both mounts share).
    • ⛔ PR fix(plugin-sharing, runtime): an email-audience share link answers every caller it does not open for with one refusal #23471's answer bytes are unchanged.
    • The maintainer is named in the triage round's list, because the detail is withheld pending them.
    • finding leaves on grading.
  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-14T13:27Z · maintainer direct dispatch: the maintainer, in session session_01LxHPTb1LxL7a6WkQbALKnD, verbatim 「23474 你负责派发。」 (opening marker 6169433344 on #6021)
    Session: session_01LxHPTb1LxL7a6WkQbALKnD
    Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-23474-email-refusal-cost
    Worktree: objectstack-issue-23474
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface: packages/plugins/plugin-sharing/src/share-link-service.ts (resolveToken), possibly packages/plugins/plugin-sharing/src/share-link-password.ts, their tests under packages/plugins/plugin-sharing/src/, and one .changeset/23474-*.md if 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 it
    Thread-read: 6168800480
    Serial constraints cleared: no open PR touches share-link-service.ts or share-link-password.ts (12 open PRs read at this claim), and neither file changed on main today. #23371 (pm:queue) is on share-link-routes.ts, which is disjoint. PR #23471's answer bytes are pinned in runtime/src/domains/share-links-refusal-order.test.ts, and this card keeps them byte-identical (triage 6168800480).

    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_planned with the reading. Disclosure: ⛔ no mechanism, inputs or reproduction recipe on any public surface. Evidence is described abstractly.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23506 @ 0088cf196f3144a3f1b89b53f015a62d7e10498b

    domain:services seat 1 · marchtian · session_01LxHPTb1LxL7a6WkQbALKnD · 2026-10-14T14:57Z, under claim 6169464027. The review was against GitHub and origin/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

    • Shape: draft, base main. The body's first line is Fixes #23474, with no other closing keyword beside an open card number. Clause-②: no is at the start of a line.
    • Scope: 3 files, inside the claim's surface: share-link-service.ts, a new pin test in plugin-sharing/src/, and .changeset/23474-email-refusal-cost.md. share-link-password.ts is untouched.
    • Changeset: @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.
    • Diff, read by the seat: in resolveToken, only the email audience's verdict moves. It is computed, the existing password step runs as before, and the two verdicts are combined into the same null. Non-email audiences 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.
    • Pins:
    • Contract review: no face is touched (no packages/spec, no governed path, no Clause-②: yes). Per contract-review.md the review of record is CI plus this seat's read. ⛔ No second agent.
    • Deviations accepted:
      • The branch was rewritten once with --force-with-lease, before any PR existed, to make the commit message and changeset abstract. The AGENTS.md multi-agent conditions were met.
      • The empty-branch push before any edit was refused by the session's classifier. The dispatch's "push nothing before red pins" governed instead.
      • Gate runs were collected in the foreground after the platform's auto-backgrounding.
      • check:* ran under the verify lock per the lane clause.
    • Known cost, accepted: an 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.
    • Out-of-scope findings: none.

    Landing (pending; the seat holds it): every check green on 0088cf19 (it was converging at this read), then pr_ready and automerge_enable through the relay. The path face is not governed, and the diff is 206 lines.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    PR #23506 queue kick: infrastructure, re-queued once · domain:services seat 1 · session_01LxHPTb1LxL7a6WkQbALKnD · 2026-10-14T15:52Z

    Machine input: merge-queue-triage 6172399177 on PR #23506, queue run 38589026391, classification failure, no aggregatable test-file signature, this PR's first queue failure.

    • Signature: job Build Core, step Upload build artifacts, first error Failed to CreateArtifact: Unable to make request: ETIMEDOUT. The step had already prepared 1767 files when the artifact service request timed out.
    • Initial judgement: a GitHub artifact-service timeout, ⛔ not this PR's code.
    • The three facts:
      1. The failing step is an artifact upload after the build. No test or source file is involved, so it is disjoint from this diff (share-link-service.ts and a pin test).
      2. Build Core was green on the 8 queue runs before this one and on the 2 latest main pushes. In this same queue run all six Test Core shards passed on the merged tree.
      3. The first error is a timeout (ETIMEDOUT), not an assertion.
    • Action: one re-queue (automerge_enable through the relay) on head 0088cf19, unchanged. ⛔ A second failure is treated as real and is not re-queued.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23506 → d1e28411 on main

    domain:services seat 1 · marchtian · session_01LxHPTb1LxL7a6WkQbALKnD · 2026-10-14T16:20Z, under claim 6169464027 and ACCEPT 6171312135.

    Reading Result
    PR object merged: true at 2026-10-14T16:19:10Z, merge commit d1e284113759db34b9dabcd18b9c9da22b32cde9
    PR timeline queued 15:24, dequeued 15:33 (an infrastructure artifact-upload timeout, audit 6172432123), re-queued once at 15:53, merged
    origin/main d1e28411 is on main. The fix in share-link-service.ts, the pin share-link-service.email-refusal-cost.pin.test.ts and .changeset/23474-email-refusal-cost.md are all on main.
    Lane reconciliation domain:services open cards: 21, the same set as before this card. Only #23474 closed.

    State change in this act: the card was closed completed by the merge. pm:dispatched and 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:servicespriority:p2Medium: important, M3security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions