Files
memona-plugin-signature/README.md
T
Danny Kim ef95c010ee
Agents / agent (pull_request) Has been skipped
Markdown files up to 96 KiB show whether their ENS signature holds
2026-09-26 06:13:00 +09:00

6.3 KiB

Memona signature plugin

Opens a WebDAV server as a folder inside Memona and signs every Markdown file saved to it with an ENS name, writing signer and signature into the file's frontmatter so anyone can check who wrote it against the name's current ENSv2 records. Signing comes from the md-ens-signature library.

The plugin is a WASI component: it speaks no sockets of its own and makes every request through the host's HTTP interface, under the connection's permissions. It registers one provider, signature, for the signed-webdav scheme.

Connecting

The connection form

Field Key Meaning
Server address url The folder's address, for example https://dav.example.com/files/
Username username Optional account name for HTTP basic authentication
Password password Optional password, stored as a secret
Signer name ensName The ENS name that signs, for example laptop.alice.eth
Signing key privateKey The private key of the address that owns the name, stored as a secret
Sepolia RPC address rpcUrl The endpoint used to check signers against ENS, by default https://ethereum-sepolia-rpc.publicnode.com
Trusted parent name trustedParent Optional. Only signers that are this name or one of its subnames count, for example team.eth

The connection's allowed hosts must include the RPC address's host as well as the server's, or every check reads as Sepolia unreachable.

Listing, reading, writing, creating directories, renaming and deleting behave as in the WebDAV plugin: reads and writes stream in pieces of at most 1 MiB, conditional writes use If-Match and If-None-Match, change polling is not supported, a rename never replaces an existing target, and a folder with contents is refused rather than removed whole.

Signing

Saving a .md file holds it whole, up to 96 KiB, and writes it once finished, with signer and signature set in its frontmatter; every other file streams straight through. The signature is EIP-191 personal_sign over the signer name and the body's SHA-256, made with the configured key; other frontmatter and the body stay byte for byte. A key that is not 32 bytes of hex refuses the connection, and a .md file that is not UTF-8 refuses the save.

A note saved in Memona, signed and verified

Verifying

Reading a .md file checks its signature against ENSv2 on Sepolia and adds an ens-verification line to the frontmatter Memona receives, which Memona shows as a badge. The owner of the signer name comes from the Universal Resolver's findOwner, so a name its parent revoked stops counting at once.

Badge Meaning
Verified: bob.team.eth The name's current owner signed this exact body
Tampered: signed as bob.team.eth The body changed after signing, or another key signed it
Unauthorized: bob.team.eth is not registered The signature holds, but no one owns the name now: it was revoked or never granted
Unauthorized: bob.team.eth is not a member of team.eth The signer is outside the trusted parent name
Unsigned The file has no signer and signature
Not checked: Sepolia unreachable The RPC address did not answer, so nothing was decided
Not checked: file too large The file is over 96 KiB, too large to hash within one call
Verified Tampered
Verified Tampered
Unauthorized Unsigned
Unauthorized Unsigned

The line never reaches the server: a save drops it before signing, and one stored on the server is replaced, so a file cannot claim its own verdict. Verdicts are reused for 30 seconds per signed body, a failed lookup included, so an editor rereading a file does not flood the RPC. Stat, list and saves keep reporting the stored size and the server's etag, so If-Match works as before.

Memona gives each call into a plugin a fixed instruction budget, which is where the size limits come from: checking hashes the body and recovers a key, so it covers files up to 96 KiB, the same as signing; a file up to 1 MiB is marked not checked, and a larger one streams through without a badge.

Building

rustup target add wasm32-wasip2
cargo build --locked --release --target wasm32-wasip2
cargo test --locked --lib

The component lands at target/wasm32-wasip2/release/memona_plugin_signature.wasm.

wit/filesystem.wit is a copy of the canonical Memona filesystem contract, and src/sdk.rs generates the guest bindings from it, so this repository builds without a Memona checkout. Refresh the copy from plugin_system/sdk/wit in the Memona repository when the contract moves, and keep apiVersion in manifest.json in step.

Packaging for the plugin store

A .memona-plugin package is a ZIP archive with the files at its root, never inside a directory:

cargo build --locked --release --target wasm32-wasip2
cp target/wasm32-wasip2/release/memona_plugin_signature.wasm plugin.wasm
zip memona-plugin-signature.memona-plugin \
  manifest.json plugin.wasm README.md LICENSE

manifest.json and plugin.wasm are required; README.md becomes the guide on the plugin's public page and LICENSE is shown beside it.

Attribution

The WebDAV code in src/lib.rs, src/dav.rs and src/sdk.rs is copied from the MIT-licensed Memona WebDAV plugin at commit 3d69750. That crate keeps its provider, reader and writer private and exports the component's entry points itself, so it cannot be wrapped as a dependency without forking it; the copy stays as close to the original as the signing hook allows, so fixes there carry over with a plain diff.