docs(specs): attachment target authorization — guards, feature split, one org scope - #117
docs(specs): attachment target authorization — guards, feature split, one org scope#117bartek5412 wants to merge 1 commit into
Conversation
… one org scope `POST /api/attachments` carries the target it is about to write (`entityId` + `recordId`), but its only gate is the tenant-wide `attachments.manage` feature. There is no seam at which "which target is this?" can be asked, so an application cannot express "a user may set their own avatar" or "only an organization admin may replace its logo" — it must reimplement route matching outside the framework. Design-only spec. Proposes running the existing mutation-guard registry from every attachment write route, splitting the overloaded ACL feature, and unifying organization resolution across the module's routes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@bartek5412 — we ran a zoom-out + prior-art pass on this spec (fresh-context second opinion, internal precedent sweep, and verified external research). Verdict: the direction is right — please keep going. Below is what confirmed it, four concrete improvements that would strengthen the spec, and context on where this lands strategically. How the direction was validated
Requested improvements to the spec
Optional: the Problem Statement can cite two more in-repo evidence points that the single-request contract leaks — AI chat uploads fake a target ( Strategic framing (so the scope boundary is clear)Stripe/Slack/Shopify solve this with two-phase upload — but two-phase relocates the target check to the attach step rather than eliminating it (Slack's One open point where your input is welcome: land Phase 1's guard call at the upload route as written (ships protection sooner, moves later), or aim it at the attach seam from the start? Current lean is the former; Jacek has the final call. |
|
w8 with, this I may have more feedback |
|
@bartek5412 follow-up from the maintainer discussion — the guards direction is confirmed (alternatives weighed below), but the discussion surfaced one real defect in the spec's design plus two structural amendments. Please fold these into the next revision. 1. Defect: guard dispatch collision between "attach to X" and "write X"The registry dispatches on a single key — Proposed fix: an additive optional discriminator on the guard input — e.g. 2. Amendment: enforce in the service, not per-routeMove the guard invocation from the six route call sites into
3. Amendment: write the guard convention into the specThis will be OM's first cross-module guard usage (existing guards are infrastructure — optimistic locking — or a module guarding its own entity), so this spec sets the precedent everyone copies. Please state:
Alternatives that were weighed and lost (for the spec's "alternatives considered")
|
|
@bartek5412 feedback given. Most important points from me:
|
Summary
Design-only spec — no implementation in this PR.
POST /api/attachmentscarries the target it is about to write (entityId+recordId), but the only authorization it runs isrequireFeatures: ['attachments.manage']— a single tenant-wide feature that cannot tell "set my own avatar" from "replace an organization's logo". Both halves of the decision are the same request, so there is no seam at which "which target is this?" can be asked. An application is left choosing between grantingattachments.manageto everyone (any signed-in account can then overwrite the organization logo, delete anyone's attachment and reconfigure storage partitions) and withholding it (invited staff can upload nothing at all, their own avatar included). The third option — an app-level target guard wired into the application's own API dispatcher, ~350 lines re-deriving core's route table — is what applications actually ship today.The spec grounds four gaps against
developand proposes three changes plus one open question:runRouteMutationGuardsalready models exactly this concern and is called bymakeCrudRoute,communication_channelsandwarranty_claims;attachmentshas nodata/guards.tsand no call anywhere. With no guards registered the registry returnsok: true, so wiring it in is behaviour-preserving for every existing install.attachments.manageis one feature for five powers — upload, delete, metadata edit, transfer, and storage-partition administration. "Any user may attach a photo" therefore also means "may delete anyone's file and reconfigure storage".DELETE /api/attachmentsuseresolveAttachmentOrganizationId(selected org, RBAC-validated, added for bug: [Attachments] —POST /api/attachmentssilently uses the uploader's default/home organization instead of the currently selected organization, causing uploaded files to land in the wrong org open-mercato/open-mercato#3765);library/[id]andtransferfilter on rawauth.orgId. A file uploaded under a non-home selected organization 404s from the library route — its owner cannot delete it. This is the one item an application cannot work around: it can refuse a request, but not redirect core's own query.matchRoutePatterncompares literal path segments case-insensitively, so/api/Attachmentsmatches. Deliberate and worth documenting, or worth tightening?Scope boundary. What this delivers is the seam, not avatar/logo rules themselves — core has no avatar feature and whether an admin may set someone else's avatar is product policy. After this lands an application expresses such a rule in ~20 lines in its own
data/guards.ts.Changes
.ai/specs/2026-08-31-attachment-target-authorization.md(design-only, status: draft)Specification
Does a spec exist for this feature/module?
Spec file path:
.ai/specs/2026-08-31-attachment-target-authorization.mdTesting
Docs-only change (one new markdown file); no build or test surface is affected. Every load-bearing claim was verified against
developat7f871e603while drafting: the absentdata/guards.tsand absent registry call underpackages/core/src/modules/attachments, the five endpoints gated byacl.ts:3, theresolveAttachmentOrganizationId/auth.orgIdsplit acrossapi/route.ts:339,682vsapi/library/[id]/route.ts:159,241andapi/transfer/route.ts:49, theattachments.*wildcard already granted insetup.ts:5, and the livedependsOncontract in thecustomersreference module. Per-surface unit and integration coverage is enumerated in the spec's Test Plan and ships with the implementation PRs.Checklist
develop.docs/cla.md). (Left for the human author to tick personally.)packages/core/AGENTS.mdupdate as part of implementation Phase 1.).ai/qa/tests/(or documented why integration coverage is not required). (Not required: no behaviour change in this PR. The spec specifies the integration coverage each phase must ship.).ai/specs/with a changelog entry (if applicable).Design System Compliance
N/A — no UI surface in this PR (one markdown file).
Linked issues
selfOwnedUserAttachmentGuardanswers that issue's closing question about who may change whose avatar.dependsOntable for the split features rather than colliding with it.