Skip to content

v0.2.35

tagged this 30 Jul 02:26
Groundwork for the operator Share/Intake UI, but two of these are live bugs that
stand on their own — the UI would only have made them easy to hit.

**Portals leaked across users of a tenant.** IntakeModule never recorded an `owner`
and listPortals never filtered on one, so a token scoped with `owner_only` listed
and could revoke every other user's portals. `/api/fm/intake/list` has been routed
all along, so this needed nothing but a direct API call. Share has had both since it
shipped; Intake now matches.

Migration is fail-closed on purpose: a pre-existing portal has no `owner`, so under
`owner_only` it goes invisible and un-revokable while staying live for its visitors
until it expires (the 90-day cap self-clears). The remedy is an operator token
without `owner_only`. The `owner` is NOT backfilled on read — that would hand
ownership to whoever lists the record first.

**A revoked portal could come back.** revokePortal used `unset` while receiveUpload
rewrites the whole tenant map on every upload, so an interleaved upload restored the
record. Revoke now writes a tombstone and putRecord refuses to write over one — the
fix Share got last week for the identical race. The read-modify-write race itself is
unchanged and still accepted; do not add a lock or a queue for it.

**Proxy-mode hosts were promised features they cannot reach.** Both adapters
forwarded allow_share/allow_intake unconditionally although none of those endpoints
are proxied, so a Laravel/WP host would render controls for routes that 404 —
exactly what allow_terminal is standalone-gated to avoid nine lines up in the same
file. Laravel now gates them (plus the share/intake link config) on standalone mode;
WordPress drops them as dead config. The `edition: pro|agency|enterprise` presets
seed those claims directly into the payload, so the proxy path unsets them too —
without that the gate looks right and leaks anyway.

**intake_base_url** finally gives intake a recipient URL; `POST /api/fm/intake`
returned none, so there was nothing to hand a visitor. Mirrors share_base_url,
falling back to the request origin — so set it explicitly behind a proxy/CDN. The
duplicated fallback is now one `ff_public_link_url()` helper, testable in free core
without a paid module.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Assets 2
Loading