domain: Micropub q=source has no scope check, create-only token can read private/draft posts #50
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
rosa/vernier#50
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found during a code review of the
domaincrate.Location:
crates/domain/src/services.rs:604(micropub_source)Severity: Security — private content disclosure
Problem
micropub_create/update/deleteeach enforceauth.scopes.contains(...), butmicropub_source(q=source) performs no scope check, and the web q=source handler gates on token presence alone. Any valid token, including a create-only token, can read the full editable source of the owner private and draft posts.Failure scenario
A user grants a client only
create; a leaked copy of that token calls q=source and retrieves the raw content of the owner private and draft posts, which the narrowed consent was meant to prevent.Suggested fix
Require an explicit read scope for q=source (decide the policy deliberately rather than by omission), ideally via a single shared required-scope gate every Micropub entry point pays.
Agent Brief
Category: bug (security — private content disclosure)
Summary: Micropub
q=sourcemust require theupdatescope; today any valid token can read the full editable source of the owner's private and draft posts.Policy decided during triage:
q=sourceis gated on the existingupdatescope. No newreadscope is introduced —q=sourceexists so editing clients can fetch a post's source before updating it, andupdateis the consent that implies that need. A create-only token must not be able to read private/draft source.Current behavior:
The domain-service entry points for Micropub create, update, and delete each enforce their scope (
create,update,delete) before doing anything else. Theq=sourceentry point does not: it resolves ownership of the post behind the givenurland returns the post, and the web handler forq=sourcegates on token presence alone. Any authenticated token — including one granted onlycreate— can retrieve the raw editable source of the owner's private and draft posts.Desired behavior:
A
q=sourcerequest made with a token that lacks theupdatescope is rejected with the same insufficient-scope error the other Micropub entry points produce (same error variant, same HTTP status and error body shape on the wire). The scope check happens before post resolution, consistent with the other entry points, so a scope-less caller learns nothing about whether the URL resolves to a post. A token holdingupdatecontinues to receive the source of posts the token's user owns, regardless of the post's visibility or draft status.Key interfaces:
micropub_source) — must enforceScope::Updatebefore resolving the post, returning the same insufficient-scopeMicropubErrorvariant the other entry points use.containschecks). Adopting that refactor is welcome but optional; if done, it must not change the scope each existing endpoint requires or the error shape it returns.Acceptance criteria:
q=sourcewith a token lackingupdate(e.g. create-only) returns the insufficient-scope error, with the same status and body shape as the other Micropub endpoints' scope failures.q=sourcewith a token holdingupdatestill returns the editable source for the token owner's posts, including drafts and private posts.q=sourcescenario.Out of scope:
read(or any new) scope to the scope vocabulary, the IndieAuth consent flow, or token parsing.q=sourcebeyond the scope gate.q=config) or the web (non-Micropub) surface.