Skip to content

security(forms): a second way of withdrawing a public form from anonymous intake is not honoured by the anonymous doors — sibling of #21331, detail withheld pending maintainer #21475

Description

@objectstack-fleet

QA-source: #21330 · access-security.public-form-intake · acceptance[6]

Sibling of #21331 (fixed by #21420) and #21468. Found while building #21468 and measured by the dispatching session on the 17.6.0 build 617f25f8: a second, separately declared way of withdrawing a public form from anonymous intake — saved successfully and shown as effective in the administrator's read — is not read by the anonymous intake doors, on every tenancy posture. Both doors keep serving and accepting the form (reproduced twice, env-wide save; still present on main after #21420).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 2, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · security · priority:p1 · domain:services · area:access · pm:queue. The withdrawal state is one state every anonymous door reads, whichever declared means set it

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-02T22:54Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ Classes and positions only. The withheld reproduction stays withheld (RUNNER rule 2).

    Why p1 and security. It is an authorization defect that fails open, the class and grade of its siblings #21331 (p1, fixed) and #21468 (p1, in flight). An administrator's declared withdrawal is shown as effective but is not honoured by the anonymous doors.

    Routing. It is the public-form intake family, domain:services, as the siblings are.

    Direction: already ruled, on #21331. Withdrawal is one state that every anonymous intake door reads.

    • A second declared means of withdrawal is either folded into that one state, or retired as declared but not enforced.
    • ⛔ No door reads one means and not the other.
    • ⛔ No second check per door.

    Pins: both directions on every tenancy posture.

    • Withdrawn by either declared means: every anonymous door refuses, and nothing lands.
    • Published: accepted. That is the control.

    The withheld detail: the claiming seat obtains the reproduction and mechanism from the holding session (session_018zT8d8NpiQ1ExhuNd5TxY6) on the maintainer's word. ⛔ They are never restated on this card or its PR.

    Serial: after PR #21473, which edits the same projection of which forms are open to anonymous intake.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (sibling of #21331 / #21468, dispatched on the maintainer's direct order)
    Session: session_018zT8d8NpiQ1ExhuNd5TxY6
    Account: hotlong (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-21475-public-form-withdrawal-second-means
    Worktree: objectstack-issue-21475
    Domain: domain:services (card label, as triage set it); expected surface packages/rest's anonymous form doors and packages/metadata-protocol's anonymous-intake projection
    File surface: packages/rest/src, packages/metadata-protocol/src, packages/qa/dogfood/test, .changeset/; packages/spec only if the key is retired rather than enforced (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default tier; no path-derived mandate)
    Clause-②: no
    Thread-read: 5962746069
    Serial constraints cleared: #21473 merged (ce532184d on main), the serial predecessor triage named

    Provenance: the maintainer, in Claude Code session session_018zT8d8NpiQ1ExhuNd5TxY6, 2026-10-03, verbatim: 「21475 也派发吧,完成后进队列合并」. This session holds the withheld reproduction; the dispatch carries it privately. The withheld detail stays off this card and its PR.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 21475,
      "status": "done",
      "branch": "claude/issue-21475-public-form-withdrawal-second-means",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21566",
      "session": "session_018zT8d8NpiQ1ExhuNd5TxY6",
      "premise_still_valid": true,
      "summary": "Premise re-measured on origin/main ce532184d: the anonymous form doors and the metadata-protocol anonymous-intake projection both judged a form on one declared switch only. Route 1 (enforce, no spec change): the rule for which form candidates a view opens to anonymous intake is now defined once in @objectstack/metadata-core (anonymousFormIntakeCandidates / anonymousFormIntakeSlugs / anonymousFormIntakeSlug / publicFormSlug) and read by both rest doors (findPublicFormView) and the protocol's org-scoped view-write refusal; the metadata-protocol local copy is deleted. The rule follows SharingConfigSchema as declared, defaults included, so raw and parsed bodies agree. Pinned both sides (unit + single and walled dogfood boots), ablated once. Patch changeset for metadata-core, metadata-protocol, rest; forms guide and the sharing.zod.ts header (plus its generated reference page) corrected. PR assignee set to the card's (hotlong) via label-write, read back matching; no labels written.",
      "tests": "All at HEAD 0164be245. metadata-core vitest 17 files / 311 passed; rest vitest 262 files / 5044 passed, 327 skipped; metadata-protocol vitest 206 passed + 3 skipped files / 3177 passed, 19 skipped; dogfood 4 public-form files / 17 passed (showcase single posture + walled posture). typecheck metadata-core, metadata-protocol, rest, dogfood: Done. dispatch-gates --commands: 114 run, 113 exit 0, check:dual-build-cjs-loads PREREQUISITE NOT MET (needs a full build) = NOT MEASURED, left to CI; --ran reconciliation 114/114. spec check:generated 15/15 current after gen:docs. eslint narrowed to the 12 changed .ts files: 12 files, 0 errors, 0 warnings (no parserOptions.project in eslint.config.mjs, so no type-aware rule can move an untouched file). Ablation: ablation-replace deleted the second-means check in metadata-core (anchor 1 -> 0, blob bf5a099a -> ad331202), rebuilt metadata-core, ablation-dist-preflight --absent: guard absent from all 12 dist files; rest 4 failed/12 passed (exactly the second-means cases), metadata-protocol 1 failed/22 passed, dogfood 2 failed/9 passed, metadata-core 5 failed/8 passed; all allowAnonymous pins and controls green. Restore: blob == HEAD, git diff HEAD empty, rebuilt, preflight found the guard back in 2 dist files, git status --porcelain empty.",
      "mcp_calls": "0",
      "api_writes": "3 — git push (not REST); fleet-write relay pr_create POST /repos/objectstack-ai/objectstack/pulls (PR 21566, draft); label-write relay POST /repos/objectstack-ai/objectstack/issues/21566/assignees; this os-dev-report comment POST /repos/objectstack-ai/objectstack/issues/21475/comments",
      "open_questions": [
        { "question": "Upgrade note: a form whose sharing does not declare public sharing as the schema defines it stops being served anonymously. The changeset states this class-level only (confidentiality). Should the CHANGELOG carry the explicit key-level migration once the detail may be disclosed?", "options": ["A: amend the changeset text before release with the explicit key and the one-line fix", "B: keep it class-level and rely on the public forms guide"], "recommendation": "A, because the CHANGELOG is the text an upgrading author greps; the seat that holds the withheld detail decides when it can be said." },
        { "question": "A published skill's description of the public-form opt-in now under-states it (Tier H surface, left out to keep this PR ungoverned). Who carries the follow-up?", "options": ["A: separate Tier H PR after this lands", "B: fold into this PR (makes it Tier H, no queue landing on green)"], "recommendation": "A, so the p1 fix can land through the queue; the follow-up is a small edit." }
      ],
      "out_of_scope_findings": [
        "class: c · reach: named producer — the published objectstack-api skill describes the public-form opt-in with fewer keys than the doors now require, so an AI following it authors a form both anonymous doors answer 404 FORM_NOT_FOUND for · evidence: skills/objectstack-api/SKILL.md public form endpoints section vs anonymousFormIntakeSlug in packages/metadata-core/src/anonymous-form-intake.ts · dedupe words: public form skill opt-in, objectstack-api SKILL public forms, anonymous form sharing keys",
        "carrier: none · noted, not filed — spec migration/conversion registry prose (packages/spec/src/migrations/registry.ts, entries/semantic/17.ui-notification-action-embed-config-retired.ts) describes the public-form gate with fewer keys; already-shipped release text, not rewritten here",
        "carrier: none · noted, not filed — the sibling console's public-forms developer page lists published forms with its own reading of sharing (static read only, not measured at a public door)"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Correction to the claim above: Clause-②: yes (widening), not no.

    The fix moved the anonymous-intake rule into @objectstack/metadata-core and exports it from that package's index (publicFormSlug, anonymousFormIntakeSlug, anonymousFormIntakeCandidates, anonymousFormIntakeSlugs, type AnonymousFormIntakeCandidate). A new export on a published package's index is a public-surface widening, so Clause-② is yes and metadata-core is graded minor (contract review on PR #21566, comment 5967245381). PR #21566's body and changeset now say so (head 769594d9c). No other part of the claim changes.


    Generated by Claude Code

  5. added a commit that references this issue on Oct 7, 2026
    6dd99b8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:servicespriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions