@coder/mux-md-client
v0.1.0-main.30
Published
Client library for mux.md encrypted file sharing with signature support
Downloads
37,914
Readme
@coder/mux-md-client
Client library for mux.md encrypted file sharing with signature support.
Installation
npm install @coder/mux-md-client
# or
bun add @coder/mux-md-clientFeatures
- End-to-end encryption — AES-256-GCM with HKDF key derivation
- Message signing — Ed25519 and ECDSA (P-256, P-384, P-521) support
- SSH key format — Parse and use standard OpenSSH public keys
- Zero-knowledge — Server never sees plaintext content or signatures
- Minimal dependencies — Only
@noble/*for cryptographic primitives
Usage
Upload content
import { upload } from '@coder/mux-md-client';
const content = new TextEncoder().encode('# Hello World');
const result = await upload(
content,
{ name: 'message.md', type: 'text/markdown', size: content.length },
{ expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000 } // 7 days
);
console.log(result.url); // https://mux.md/Ab3Xy#key...
console.log(result.mutateKey); // Store for deletion/expiration updatesDownload content
import { download } from '@coder/mux-md-client';
const { data, info, signature } = await download('https://mux.md/Ab3Xy#key...');
console.log(new TextDecoder().decode(data));
if (signature) {
console.log('Signed by:', signature.publicKey);
}Upload with signature
Signed uploads embed plaintext as JSON ({ content: string, sig: SignatureEnvelope }), so the
content must be valid UTF-8 text.
Option A: let the library create the signature
import { upload } from '@coder/mux-md-client';
const content = new TextEncoder().encode('# Signed Message');
const result = await upload(
content,
{ name: 'signed.md', type: 'text/markdown', size: content.length },
{
sign: {
privateKey, // Uint8Array (Ed25519: 32 bytes)
publicKey: 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...', // OpenSSH public key
githubUser: 'username', // optional attribution
},
}
);Option B: provide a precomputed SignatureEnvelope
import { upload, createSignatureEnvelope } from '@coder/mux-md-client';
const content = new TextEncoder().encode('# Signed Message');
const signature = await createSignatureEnvelope(
content,
privateKey,
'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...',
{ githubUser: 'username' }
);
const result = await upload(
content,
{ name: 'signed.md', type: 'text/markdown', size: content.length },
{ signature }
);Option C (Node.js): sign via SSH agent (e.g., 1Password)
import { upload } from '@coder/mux-md-client';
import { createSshAgentSigner } from '@coder/mux-md-client/ssh-agent';
const content = new TextEncoder().encode('# Signed Message');
const signer = await createSshAgentSigner({ githubUser: 'username' });
const result = await upload(
content,
{ name: 'signed.md', type: 'text/markdown', size: content.length },
{ sign: { signer } }
);For implementation details and rationale, see the plan that added SSH-agent (1Password) signing:
https://mux.md/dfIDZ#9UllGCQoi2V7NQ
Verify signatures
import { parsePublicKey, verifySignature, base64Decode } from '@coder/mux-md-client';
const parsedKey = parsePublicKey('ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...');
const sigBytes = base64Decode(signature.sig);
const messageBytes = new TextEncoder().encode(content);
const valid = await verifySignature(parsedKey, messageBytes, sigBytes);Delete or update expiration
import { deleteFile, setExpiration } from '@coder/mux-md-client';
// Delete file
await deleteFile(result.id, result.mutateKey);
// Update expiration
await setExpiration(result.id, result.mutateKey, Date.now() + 30 * 24 * 60 * 60 * 1000);
// Remove expiration
await setExpiration(result.id, result.mutateKey, 'never');API Reference
Client Operations
| Function | Description |
|----------|-------------|
| upload(data, fileInfo, options?) | Encrypt and upload content |
| download(url, key?, options?) | Download and decrypt content |
| getMeta(url, key?, options?) | Get file metadata without downloading |
| deleteFile(id, mutateKey, options?) | Delete a file |
| setExpiration(id, mutateKey, expiresAt, options?) | Update file expiration |
| parseUrl(url) | Parse mux.md URL into id + key |
| buildUrl(id, key, baseUrl?) | Build mux.md URL from components |
Signing
| Function | Description |
|----------|-------------|
| createSignatureEnvelope(content, privateKey, publicKey, options?) | Create a signature envelope for upload |
| signEd25519(message, privateKey) | Sign with Ed25519 |
| signECDSA(message, privateKey, curve) | Sign with ECDSA |
| verifySignature(parsedKey, message, signature) | Verify a signature |
| parsePublicKey(keyString) | Parse SSH public key |
| computeFingerprint(publicKey) | Compute SHA256 fingerprint |
| formatFingerprint(fingerprint) | Format fingerprint for display |
Node-only SSH agent helpers
These helpers are available from @coder/mux-md-client/ssh-agent (Node.js only):
| Function | Description |
|----------|-------------|
| createSshAgentSigner(options?) | Create a signer callback compatible with upload(..., { sign: { signer } }) |
| createSshAgentSignatureEnvelope(data, options?) | One-shot helper that returns a SignatureEnvelope |
Types
interface FileInfo {
name: string;
type: string;
size: number;
model?: string; // AI model (e.g., "claude-sonnet-4-20250514")
thinking?: string; // Thinking level (e.g., "medium")
}
interface SignatureEnvelope {
sig: string; // Base64-encoded signature
publicKey: string; // SSH format public key
githubUser?: string; // Optional: claimed GitHub username for attribution
}
type KeyType = 'ed25519' | 'ecdsa-p256' | 'ecdsa-p384' | 'ecdsa-p521';Publishing
This package uses NPM Trusted Publishing with OIDC for automated CI/CD deployments. No NPM tokens are required - authentication happens via GitHub Actions OIDC.
Initial setup (one-time, requires npm account with publish access to @coder scope):
Create the package on npm with an initial publish:
cd packages/client npm publish --access publicConfigure trusted publisher on npmjs.com:
- Go to https://www.npmjs.com/package/@coder/mux-md-client/access
- Click "Trusted Publisher" → "GitHub Actions"
- Configure:
- Repository:
coder/mux-md - Workflow:
npm-publish.yml - Environment: (leave blank)
- Repository:
Subsequent publishes happen automatically when
packages/client/**changes are pushed tomain.
License
MIT
