Client library for mux.md encrypted file sharing with signature support
npm install @coder/mux-md-clientClient library for mux.md encrypted file sharing with signature support.
``bash`
npm install @coder/mux-md-clientor
bun add @coder/mux-md-client
- 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
`typescript
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 updates
`
`typescript
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);
}
`
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
`typescript
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
`typescript
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)
`typescript
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
`typescript
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);
`
`typescript
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');
`
| 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 |
| 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 |
`typescript
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';
`
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):
1. Create the package on npm with an initial publish:
`bash`
cd packages/client
npm publish --access public
2. Configure 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-mdnpm-publish.yml
- Workflow:
- Environment: (leave blank)
3. Subsequent publishes happen automatically when packages/client/** changes are pushed to main`.
MIT