FEP-5624: Per-object reply control policies

Emedded Accept can’t be revoked unless there’s an expiration date – another reason why I was thinking about introducing new object type. If replyApproval contains expired permission object, recipient should fetch it by ID to see if it is still valid.

My post was in part inspired by reading zcap spec… Perhaps something simpler and less powerful has a better chance of being adopted?

I want to have an inbox that can reject Note with mention if it doesn’t carry a valid permission object. This is an anti-spam measure: I expect that sooner or later spammers will start to mass-send activities that generate notifications, and currently the only solution we have is shared blocklists.

maybe revocation is not exactly the right word, but wouldn’t you invalidate all existing proofs that reference your key if you change the key? this doesn’t have anything to do with object types as far as i can see.

again, do you reject if any Mention is missing some signature? this seems like a good way to break receipt of any mentions unless every single implementation rolls out the exact same scheme as you. and i can’t see any value in having some mentions verified, but some mentions not. and furthermore, why do we even add special handling to Mentions at all? you can already mass-send activities that spam lots of people using to/cc/audience.

this is why i said that “mention control” is a red herring. a Mention should be basically no different than a Link. <a href="https://trwnh.com">trwnh</a> is a “mention” in HTML, and you can use the Webmention protocol to notify and aggregate such mentions.’

you may more generally consider the problem of “link control”. basically, unless the link has some special qualification, it doesn’t make any sense to ask for approval unless you explicitly want to signal that you obtained prior consent from the controlling authority.

the reason we are trying to do this for replies is because replies have a semantic quality of being part of some larger conversation, and we don’t want to bottleneck ourselves on only getting this conversation from the conversation authority. having the “reply approval” allows you to distribute updates to someone else’s conversation.

this pattern doesn’t work for mentions because mentions don’t have those semantics – they’re basically just regular links. the semantic of a mention is that the “name” (inner content) in some way identifies the “href” (the hyperlinked reference). so in the HTML example from before, trwnh is a Mention of https://trwnh.com. this statement is in no way dependent on the authority of https://trwnh.com; it’s more like a nickname in your address book. in plain english, you could equivalently say “every time i mention trwnh, I am talking about the subject of the resource https://trwnh.com.” in fact, you can mention someone without even addressing them or sending them the document that mentions them! you don’t have to let someone know that you mentioned them. this only fails if you tie mentions and addressing together (which i think you shouldn’t necessarily do).

for the purposes of “anti-spam”, you can only depend on your own internal policies. you may consider dropping any post that is addressed to too many people, like pleroma’s “hellthread mrf”. or, you may consider dropping any activity addressed to you that didn’t come from someone in a collection you maintain, like only allowing messages from people you follow. or, instead of dropping messages, you may decide to process them successfully but otherwise suppress notifications. it’s entirely up to you.

in any case, the scope of this FEP is specifically about the inReplyTo metadata field, and giving third-party observers a way to construct conversations with some kind of authority considerations. coincidentally, this is why we/i talked so much about conversations above – the abstraction of the “conversation” allows us to specify an authority who controls that conversation. you can consider a similar problem that FEP-400e tried to solve but with less particulars: how do you know an object is part of a collection without having that collection?

The key doesn’t change, instead the whole “permission object” expires. This can be compared to JSON Web Token which also can have expiration time, or Verifiable Credential. Actually, VCs seems to be exactly what I was looking for.

Maybe I’ll accept it but won’t generate notification

I understand that mentions are just Links, but they do generate notifications and this behavior is very common in Fediverse. My point is that any activity that can generate notifications can be used for spamming.

Currently the only effective anti-spam policy is blocklist. If the message can carry a credential (capability, token), the space of possible policies vastly increases, because credential can come from an external system. In other words, this enables interoperability with other identity and reputation systems.

I meant that some implementations like Pleroma can do blind key rotation, which would mean that any earlier signatures would no longer validate.

Generating notifications is entirely an app policy. Spam is generally a function of deliverability. The way to combat spam is to reduce deliverability. We can reduce deliverability by using capability-based security to prevent delivery in the first place, or we can use policies to drop activities after delivery but before receipt. examples:

  • we can choose to hand out our inbox URI only to trusted contacts. we might even have multiple inboxes, like a public inbox and a private inbox, and different inboxes can have different policies.
  • we can require some shared secret to be used as a bearer token. activities posted without a valid token might fail or be deprioritized or greylisted.
  • we might have a server-side filtering policy that checks incoming activities for certain properties, such as who the actor is, or what the content includes, or whether certain logical conditions are met.

anyway, this is a bit off-topic so maybe we could make a new thread for further discussion of anti-spam policies, if you wish?

2 Likes

This is a big departure from how the feature works on Twitter today, and it would be a significant step back in terms of usability. The most common usecase for this feature is something like: “I made this post for my 250 followers, it went viral, now I have hundreds of people replying to it every minute and I need them to STOP TALKING because I can’t respond to every stupid thing somebody decided to attach to my post.”

(Twitter actually has two features designed to address this use-case, both with their own pros and cons)

1 Like

I think it’s better to return to the original topic. To summarize, the idea is to use some kind of signed object with expiration date to represent approval. This object can be inlined to make verification possible without making request to authority server.
To make approval revocable, authority can make it short-lived (for example, approval may expire in 30 minutes). Expired approval can’t be verified offline, so authority can revoke it by returning 404 in the same way it can revoke approval in the form of Accept activity.

The main advantage, in my opinion, is the possibility of offline verification.

Yeah, there’s also “i am not accepting responses, don’t at me” and “i am okay with feedback but i want to manually review everything and only let through the thoughtful responses” or even “when i block someone, i don’t want them to show up under my posts, not just for me, but for other people too”

One-time approval is not really good enough to handle all these use-cases.

this should be possible if you have a signed object that you can embed, yes.

this is also possible but imo not needed? it’s like saying “you can distribute this, but only for a limited time”. it is possible to interpret it in such a way: “well, this object used to be approved, but that approval has expired, i don’t need to check what’s at the node id given because i already have that data”. the problem with embedding is one we’ve covered already, in that it is unclear whether the representation is partial or outdated, and there is no guarantee that the node is fetchable separately. a generic JSON-LD parser would not know to re-fetch an expired proof, i think?

Sorry I left this hanging for so long, I’ve been busy with a lot of stuff, but this conversation has been brought up again and I have some more thoughts to share.

On controlling quotes

I understand what you mean, but I think there’s still value in denying the convenience to people who may be abusive. And more important, things like Twitter allow listing quote-posts from the quoted post: such a feature may lend the quote-poster your audience, in a way similar to replies, so it makes sense to me that we’d have similar mechanisms for controlling who can quote-boost you, or at least be listed from your post.

Back to controlling replies

Back to controlling replies, Erin pointed out that Accept may be too generic and end up ambiguous or opening to cross-purpose attacks.

I tend to agree. I wanted to do something quite generic, but there is value in making something explicit and straightforward.

I’m still not satisfied with the state of the FEP regarding defining who is the authority that gets to decide whether a reply is allowed, but I think even if not ideal, having it the author of the post being immediately being replied to has the benefit of being extremely simple and still providing a lot of value.

I guess I can try to abstract inReplyTo occurrences to something that can be overridden by a subsequent FEP defining another reply model (such as by defining replyTo and context or conversation).

I think there may be a simpler approach to reply moderation—it wouldn’t provide full moderation of every incoming reply, but it would solve many common issues while being much easier to implement and understand.

Ignore Accepts and replyApproval and conversations, and just focus on one feature: sending a Reject activity with a reply as its object.

  • You can only Reject replies to your own posts (no concept of a larger conversation to track)
  • Reply Rejects are federated widely, like Deletes
  • A Reject disconnects a reply from a thread
  • You can configure your server to auto-reject new replies, or replies from certain users, as soon as it receives them
  • You can Undo a Reject to approve a reply that was mistakenly auto-rejected
  • canReply would still be supported as a hint to hide the reply button
  • You could also reject Likes and Announces, and they won’t appear in your “Likes and Boosts” lists
  • To avoid the problem of fediverse-wide reject spam, an object could instead have a rejections collection containing Rejects for all related replies/likes/announces

Sounds like Signaling side effects asynchronously by generalizing Accept/Reject maybe?

In any case: only post-facto rejection of replies doesn’t work for the use-cases described. specific comments below:

this vastly increases traffic that would be considered not only useless, but counterproductive. you are basically advertising the existence of a reply.

similarly: just don’t advertise any unwanted Likes or Announces.

this could work in theory but it would be a bad idea. if you’re going to check a collection at the origin, then just check the actual replies/likes/shares


aside 1: what is a “quote” and how does it relate here

if “quotes” were just replies that embedded the replied-to object, then “reply controls” would also be “quote controls”. i would argue there’s actually near-zero difference between a quote and a reply; a quote is just a reply with an embed. one only needs to look at how a “reply” is rendered in the IndieWeb space to see the similarity to “quotes”. See https://aaronparecki.com/replies for an example of this.

i don’t think it makes any sort of sense to try and prevent people from typing or pasting a hyperlink into the content; on that front, what we might seek to adopt countermeasures for would be to disable embeds or previews via OEmbed.

offtopic: consider a toot:quoteReply boolean that, if true, indicates that the inReplyTo object should be embedded above while rendering the current object. mastodon could then change/relax its policy of not showing replies in feeds, and insert such posts into the feeds of followers (or make it an option? idk, this is an implementation detail, and other implementations already show all replies anyway)

aside 2: about contexts and conversations

if you had a first-class concept of a “conversation” that isn’t just a reply-chain, then such a “quote” may or may not be part of the same discussion (copy the context), or it may be part of a new discussion (set your own context), or it may be not a part of any discussion (in the default global context of null). seeing as how it makes sense to have a concept of a “moderated conversation” or “authoritative context”, we derive the following consequential cases:

  • support reply stamps/proofs/approvals, proving that a reply was added to inReplyTo.replies
  • support context stamps/proofs/approvals, proving that an object was added to context
  • support both

aside 3: Accept or Add?

so far the discussion and proposal has mostly assumed an Accept activity would serve the purpose… and it very well might. but wouldn’t an Add do the same as well? in fact, an Add could be distributed to the followers of the person being replied to, as well as to the person replying; the person replying can then reference that Add activity as proof, or even inbox forward it to their followers.

for revocation, i don’t think this is actually any different either; you just stop serving the “proof” activity, regardless of its type. if anything, you can explicitly send a Remove to your followers, and possibly to the author of the removed reply. if they are a good actor, they might forward it to their followers. if they are a bad actor, they might harass you about it and get blocked/reported.

it would also probably address this concern:

if you send an Add to replies, then that’s unambiguous.

based on a discussion I had elsewhere: the generalization of this pattern is “how do we prove an object is part of a collection”, where the salient semantic “special collections” might be:

  • Object.inReplyTo.replies
  • Object.context
  • Announce.object.shares
  • Like.object.likes

in each of these cases, you essentially have only one side of a bidirectional claim. if you want to verify the other side of the claim, then the following will do:

  • if you have access to the collection or an authoritative representation of it: iterate over items/orderedItems until you either find the object in question, or until you’ve run out of items.
  • if you have access to the Add activity and can verify its provenance: check the object/target references match.

obviously the latter is much easier. the only point of contention is how to discover this Add, and how to verify its provenance.

  • for provenance, the current model of fedi allows an activity to be proven if it is delivered from origin, fetchable from origin, or signed for redistribution

it may not be possible to unify all such cases in one property, so the “best” we have currently is like so:

replyProof

for proof in replyProof:
  assert "Add" in proof.type
  assert proof.object.id == id
  assert proof.target.id == inReplyTo.replies.id

contextProof

for proof in contextProof:
  assert "Add" in proof.type
  assert proof.object.id == id
  assert proof.target.id == context.id

announceProof

for proof in announceProof:
  assert "Add" in proof.type
  assert proof.object.id == id
  assert proof.target.id == object.shares.id

likeProof

for proof in likeProof:
  assert "Add" in proof.type
  assert proof.object.id == id
  assert proof.target.id == object.likes.id

exact property names subject to change, of course.

I have actually suggested something along the lines of "NS:replyDisposition": "NS:Quote" or similar as a way of signalling quote posts in the past. I think there is a lot about this model which is good!

I dislike this approach because it requires indexing the URL of the reply collection in order to process efficiently. If I receive an Add with the target being an object’s reply collection, I have to identfy that the target is that special purpose collection.

I prefer a special purpose activity for this reason. We shouldn’t be scared of vocabulary extensions. In fact, I think it makes things easier to be able to just recognise the type rather than having to infer the behaviour from the URL.

If I could do a ground-up backwards incompatible redesign of AP, I would ideally un-reify things like inboxes, outboxes, reply collections, etc; which are properties of objects more than distinct objects

1 Like

is this any different than what is already done for followers collections and “followers-only” posts? it seems to me like “special purpose” collections are, well… special. isn’t it worth it? at least for context modeling a conversation, you would need to index that anyway, right? but then again, is it really required to efficiently track everything? the argument is stronger for context or replies, but you could make it for any collection, no? (for what it’s worth, my conception is that every collection should be indexed by id, just like every object. assuming you’re using a database at all, that is.)

sure, “we shouldn’t be scared of vocabulary extensions”, but we also don’t need to make unnecessary ones. there’s no need for a Reply activity when inReplyTo and replies exist. “Add to replies” should be a valid way to advertise that someone replied to your post. you could forward the original Create, but you shouldn’t have to. same with Like and Announce.

aside from that, i’m afraid i don’t understand your issue with inbox/outbox/etc collections – if i were starting greenfield and didn’t care about compatibility with mastodon et al, i would rely on them more, not less. more things should be collections. your profile’s stream of posts should be a separate collection than the outbox. this would allow hiding posts by doing a Remove instead of being forced to do a Delete. you could have a more natural paradigm of “unlisted” posts a la youtube. and so on.

The followers collection has special behaviour, yes; but in terms of delivery it’s not really any different from any other arbitrary collection. The fact that most servers don’t allow creation of or addressing to arbitrary collections is irrelevant to the protocol semantics here, if that makes sense?

Semantically it can be modeled that way, but similarly follow request semantics aside there’s no reason you can’t model following someone as {Add, object: personImFollowing, target: myFollowersCollection}; or a Like as {Add, object: objectImLiking, target: myLikesCollection}.

However, the AS2 spec makes these distinct for a reason

The distinction I’m making here is sort of between “objects” and “Objects”. If I could have a complete do-over of the protocol (and maybe RDF, but that’s an even bigger can of worms) I would imbue the protocol with an explicit sense of nesting - i.e. a way to refer specifically to a given user’s followers collection or a given post’s replies collection, etc.

Effectively there are objects in AP/AS2 which are “synthetic” - things like inboxes, outboxes, likes/liked, followers/following, replies, keys - in that they exist only in relation to a parent object, and I’d make that explicit.

Given we can’t do that, I think it makes more sense to define specialised activity types (Like AS2 itself does!) when working with the synthetic collections

(My initial thought was also {Add, object: the reply, target: the parent post's replies collection}, but I realised that the difficulty with this is that it forces implementations to index the replies collection specifically)


One other reason I prefer an explicit ApproveReply activity is that it makes future extensions less fraught - you don’t have to worry about how the semantics work with a generic Approve or Add activity, you don’t have to worry about cross-purpose attacks, etc

distinct in the original action, because they have side effects. there are no special side effects for a reply; a reply is just metadata. even so, i’m not talking about your own follows, likes, etc – i’m talking about making your followers aware of someone else’s activity. there are two ways this could be done:

  • a) forward the activity verbatim. the original actor needs to deal with provenance via signatures or making it fetchable from origin.
  • b) add the activity to one of the special collections. the receiving actor needs to be able to recognize from id that this is a special collection if they want to apply special semantics.

a slightly different option is to look at the object of the Add, ignoring the semantics of the Add itself if you don’t actually maintain collections as activitypub expects you to. if the object is an Activity, process it. if the object is an Object, wrap it in a Create and process it. this is a limited approach, sure, because to do any more than that you actually need to have a concept of collections. but this should get you far enough to detect other people’s replies/in-context posts, likes, announces.

it’s different in that with sharedInbox, the recipient server is expected to know or calculate the contents of the collection for itself. this is probably why arbitrary collection addressing hasn’t taken off yet; you have to give up on sharedInbox in order to do it.

We really could use an envelopedInbox or similar for this purpose, which carries “envelope recipients” information (ala SMTP); but that’s off topic

I’m going to agree with @trwnh here that it makes more sense to list things you accept than a list of things you reject. There’s also the fact that Delete propagation relies on third-parties relaying the messages for you, and that is not entirely reliable. This would be worse in the case where you’re explicitly relying on the collaboration of the server whose replies you want to reject.

That proposal, however, does save a round-trip when the reply is accepted.

I’m going to agree with @erincandescent that the more explicit the activity, the better it is. We already have some guesswork around existing Accept and Reject patterns to know what they are about, and it’s already a pain. I think it would be easier to implement (and maintain) support for new activities if it were something different and more explicit.

Updating the FEP

I finally pushed a PR updating the FEP to explicitly support the case where the authority directly forwards the implicitly approved replies.

The PR also changes the use of Accept and Reject to more explicit ApproveReply and RejectReply to avoid any possible ambiguity with other extensions.

Unfortunately, the main sticking point of defining the “distribution authority” is still not addressed.

On quote posts

As for quote posts, I guess they could be considered as replies. In any case, the more I think and gather feedback about them, the more I think we do need some kind of approval/rejection mechanism for them too. For instance, Twitter apparently stops embedding a post when the person quoting it has been blocked by the person being quoted, reverting to a simple link without any preview. From what I’ve been told, this has not always been the case, and that change contributed to greatly reduce dogpiling caused by that feature.

Unfortunately, in a federated setting, we cannot easily achieve the same thing. Revocable explicit approvals would greatly help, but would still largely rely on the good will of the previously-approved-but-now-blocked account’s server to redistribute the revocation.

1 Like

[Deleted PR reference, since the PR is now merged.]

Wouldn’t this break federation with forums and groups and Facebook-style platforms?

The typical permission model on a forum is that the forum administrator determines who can post and who cannot post in the forum. Someone who comments on a thread has no authority to override the administrator’s policies, so forums would not implement permissions on a per reply level. Doing so would break thread-based discussions.

It works the same with with private or semi-private groups, or Facebook-style platforms, except in that case it is typically the conversation starter that has authority over who replies. If the person who started the thread wants to turn off discussion, then no one can reply to any of the comments or replies. A person who replied to the original thread cannot override this decision. Comments and replies are turned off.

So, what happens when a forum or group or Facebook-style platform does not implement this proposal since that is not how permissions work on those platforms?

@WisTex all good questions. Think of it as – this FEP just expresses the data model of how to express the preferences. And then the who gets the say (forum administrator, or the original poster) depends on each usecase and instance and implementation.
Meaning, these are orthogonal, so as long as our data model is flexible enough to express the different options.

1 Like