-
Notifications
You must be signed in to change notification settings - Fork 1
prevent outside modifications to the thread Y Map #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Two approaches we discussed with Kevin: The server can reject the change (by inspecting what is inside of the update), by not applying it to the document, but we would need to implement something on the protocol level to say that the change failed to apply and give the client a way to move from this invalid state (because further changes to the document from this point would all also be invalid). Or we can "rollback" the change on the server making the user's change effectively moot, by running the inverse of whatever change the client made. We should take care to apply both the invalid change, and it's inverse within a single "transaction" so that no other clients can see this invalid state. Kevin expressed preference towards the first option, though we could not figure out a better idea to move a client from an invalid state than "reload the window". We'll want to put some thought towards this to see what we actually expect in this sort of a scenario. |
@dmonad could you share here what's a good way to inspect how we see which map an update is affecting? Then we can detect the "invalid" update and explore either the two approaches nick suggested. PS: if you're interested, this is the POC code for a comments REST server: https://github.com/TypeCellOS/BlockNote-demo-nextjs-hocuspocus/blob/a55cd0e4ca635d812bd3b119c856215065c2e1cb/hocuspocus-server/src/threads.ts |
Yjs does not support type-level permissions. Permissions can only be applied on the global document. Rejecting changes based on the observe event opens up several opportunities for exploits and bugs. I suggest implementing a REST API if you need control over certain actions. The server then can manipulate the Yjs document for the client. But if you want to roll-back changes anyway, this is how I would do it: const ydoc = new Y.Doc()
const restrictedType = ydoc.getArray('restricted')
/**
* Assume this function handles incoming updates via a communication channel like websockets.
* Changes to the `ydoc.getMap('restricted')` type should be rejected.
*
* - set up undo manager on the restricted types
* - cache pending* updates from the Ydoc to avoid certain attacks
* - apply received update and check whether the restricted type (or any of its children) has been changed.
* - catch errors that might try to circumvent the restrictions
* - undo changes on restricted types
* - reapply pending* updates
*
* @param {Uint8Array} update
*/
const updateHandler = (update) => {
// don't handle changes of the local undo manager, which is used to undo invalid changes
const um = new Y.UndoManager(restrictedType, { trackedOrigins: new Set(['remote change']) })
const beforePendingDs = ydoc.store.pendingDs
const beforePendingStructs = ydoc.store.pendingStructs?.update
try {
Y.applyUpdate(ydoc, update, 'remote change')
} finally {
while (um.undoStack.length) { um.undo() }
um.destroy()
ydoc.store.pendingDs = beforePendingDs
ydoc.store.pendingStructs = null
if (beforePendingStructs) {
Y.applyUpdateV2(ydoc, beforePendingStructs)
}
}
}
// whenever you receive updates from a remote peer:
updateHandler(update) |
Thanks! Is there also an easy way to detect beforehand if the Asking because if we were to implement a workaround like this, I think it's best to keep it as isolated as possible |
The We could inspect the update directly, read the content using the binary decoder, and check whether any operation affects |
Ok, thanks! |
Just to be clear, if a client made a change to this I'm curious on how (if possible) we could do the inverse of this (i.e. instead of protecting a single |
We need to distinguish two cases: RestYjsThreadStore YjsThreadStore |
Good to explicitly state, I was just curious if there was a "cheap" way to do the YjsThreadStore.
Yep, I think it would, too. |
The above solution uses the undo manager to revert the change. So the document wouldn't be in a broken state.
In this case, I suggest that the undo manager still observes the "comments" type or "restricted" type. Then you register an observer on the type and only trigger Something like.. let revertChanges = false
comments.observeDeep(events => {
revertChanges = events.some(event => !isAllowedChange(event))
})
Y.applyUpdate(ydoc, update)
comments.unobserveDeep(..)
if (revertChanges) {
um.undo()
} |
I'm unsure how helpful this is, but I felt like it was valuable to diagram how the system should work, in the interest of aligning expectations. REST Proposal for CommentingThis is an overview of how the REST architecture could work for a commenting system based on Y.js This approach uses different permissions for comments that are separate from the document content. It side-steps issues with comments by only granting Benefits
Downsides
Future E2EE System for CommentsIn the interest of thinking of things ahead of time, it is probably valuable to see how an E2EE system for comments might work. In an E2EE system, each user would have 1 or more public keys which are securely shared to other users within the system. Their device would maintain the private key, which they can use to sign messages they make. For the moment, we will gloss over the implementation of a E2E for Y.js and focus on a possible implementation for commenting. The main idea of how comments might work in a system like this is the fact that each operation a user wants to make for comments would be signed by their private keys, and other users in the system would validate the comments to make certain that they are authentic to the associated public key. For a system like this, it is safe to give write access to the comments document, because each message much be signed and can be independently validated for authenticity. Benefits
Downsides
ConclusionUnfortunately, there isn't a clear way to build towards the future for this system as the main mechanism for validating authenticity is not present in the current system. Theoretically, for a migration between the two systems, it should be relatively straight-forward to re-sign existing comments (either by users or more likely by the docs server as a migration step). |
Thanks for the write-up! My remarks will be about the first part as I think indeed E2EE is out of scope for now.
fyi, this has already been implemented here: server, client Separate doc vs same doc for comments:In your diagram, you describe a separate Y.Doc for Comments. We didn't discuss the se Let's explore the pros / cons of using a separate Y.Doc vs storing the Comment data inside the same Y.Doc as the document (as a Y.Map): Separate Doc
Same Doc
Securing YjsThreadStoreNote that in your comment above, we didn't discuss Next stepsI'd propose to:
|
BlockNote-demo-nextjs-hocuspocus/hocuspocus-server/src/index.ts
Line 32 in a55cd0e
The text was updated successfully, but these errors were encountered: