npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@absolutejs/secure-transfer

v0.3.0

Published

Provider-neutral encrypted, record-oriented large-object transfer for AbsoluteJS.

Readme

@absolutejs/secure-transfer

Provider-neutral encrypted large-object transfer for AbsoluteJS. It splits a known-length source into bounded, independently authenticated records, writes only ciphertext to an untrusted store, and downloads into a transactional sink.

const transfer = createSecureTransferClient({
  cryptoProvider,
  store,
  policy: {
    maximumAttachmentBytes: 1024 ** 4,
    maximumDescriptorBytes: 16 * 1024,
    maximumFutureSkewMs: 300_000,
    maximumMetadataBytes: 4 * 1024,
    maximumRecordPlaintextBytes: 1024 * 1024,
    maximumRecords: 1_048_576,
    maximumTtlMs: 7 * 24 * 60 * 60 * 1000,
  },
});

const descriptor = await transfer.upload({
  attachmentId: crypto.randomUUID(),
  body: file.stream(),
  byteLength: file.size,
  contentType: file.type,
  conversationId,
  expiresAt: Date.now() + 86_400_000,
  fileName: file.name,
  senderDeviceId,
});

await messaging.send({
  conversationId,
  id: crypto.randomUUID(),
  plaintext: encodeSecureTransferDescriptor(descriptor),
  purpose: "secure-transfer.descriptor",
  ttlMs: 86_400_000,
});

Authenticated byte ranges

downloadRange() authenticates every complete encrypted record covering the requested interval, then passes only the selected plaintext bytes to a transactional range sink. The range is [start, endExclusive) and must be non-empty and within the descriptor's declared plaintext size.

await transfer.downloadRange(
  descriptor,
  { start: 1_048_576, endExclusive: 2_097_152 },
  rangeSink,
);

This proves the authenticity and position of the requested records against the descriptor. It intentionally does not fetch or prove the current availability of records outside the range.

Honest revocation

Configure a trusted SecureTransferRevocationStore, then create the durable tombstone before attempting ciphertext cleanup:

const { revocation, ciphertextRemoved } = await transfer.revoke({
  descriptor,
  reason: "member-removed",
  revokerDeviceId,
});

await messaging.send({
  conversationId,
  id: crypto.randomUUID(),
  plaintext: encodeSecureTransferRevocation(revocation),
  purpose: "secure-transfer.revocation",
  ttlMs,
});

Recipients must authenticate the E2EE sender, authorize that device to revoke the attachment, strictly decode the notice, and only then call applyRevocation(). The notice is bound to the exact descriptor by a SHA-256 hash. Downloads consult trusted policy state before and throughout retrieval and fail closed when that store errors. Keep tombstones at least through the descriptor expiry; ciphertextRemoved: false means cleanup must be retried even though cooperating clients already block the transfer.

Revocation is not retroactive cryptographic erasure. A bearer capability, ciphertext, or plaintext already copied by a recipient cannot be recalled. After an MLS member removal, use a fresh capability for replacement content and deliver its descriptor only in the new epoch; removal protects future epoch traffic, not secrets the former member already received. This follows the epoch and member-removal model in RFC 9420. Where a deployment uses cryptographic erase for storage cleanup, follow the key sanitization program in NIST SP 800-88 Rev. 2; that still does not sanitize independently held recipient copies.

Post-membership capability replacement

An MLS removal changes who receives future epoch secrets, but it does not change an attachment capability already delivered in an earlier epoch. For retained attachments that a removed device must no longer fetch, upload a new encrypted copy with a fresh capability and send the replacement only in the new epoch:

const membership = await messaging.removeMembers({
  conversationId,
  deviceIds: [removedDeviceId],
  ttlMs,
});

const replacement = await transfer.prepareReplacement({
  previousDescriptor,
  reason: "membership-change",
  replacement: {
    ...replacementMetadata,
    body: reopenPlaintextSource(),
    senderDeviceId,
  },
  securityEpoch: membership.epoch,
});

await messaging.send({
  conversationId,
  expectedSecurityEpoch: membership.epoch,
  id: crypto.randomUUID(),
  plaintext: encodeSecureTransferReplacement(replacement),
  purpose: "secure-transfer.replacement",
  ttlMs,
});

await transfer.activateReplacement({
  authenticatedContext: {
    conversationId,
    purpose: "secure-transfer.replacement",
    securityEpoch: membership.epoch,
    senderId: senderDeviceId,
  },
  persistReplacement: saveProtectedDescriptor,
  previousDescriptor,
  removeSupersededCiphertext: true,
  replacement,
});

The secure-messaging send persists advanced MLS state and its retryable outbox entry before returning, even when delivery is queued. Only then should the sender activate supersession and remove old ciphertext. A recipient strictly decodes the same payload and passes the message's authenticated context to activateReplacement(). Activation verifies the old descriptor hash, fresh transfer ID and capability, attachment, conversation, sender, purpose, and exact epoch. Its persistReplacement callback runs before the old tombstone is installed and must be durable, idempotent, and protect the bearer descriptor.

If activation crashes after descriptor persistence but before supersession, retry the same payload. This temporarily leaves the old transfer usable instead of stranding the replacement. Expiry sweeps clean abandoned new ciphertext when a message can never be durably queued. Rate-limit rotations and prioritize only attachments that remain useful; membership churn must not become an unbounded re-encryption denial of service.

Resumable uploads

Configure a SecureTransferReceiptProtector and SecureTransferProtectedReceiptStore, then persist the initial protected receipt before reading the source:

const { receiptId } = await transfer.beginResumableUpload({
  attachmentId,
  byteLength: file.size,
  conversationId,
  expiresAt,
  fileName: file.name,
  senderDeviceId,
});

const descriptor = await transfer.resumeUpload({
  receiptId,
  source: (byteOffset) => file.slice(byteOffset).stream(),
});

Receipts contain the transfer's bearer decryption capability. Core passes only protected opaque bytes to receipt storage and binds protection to receiptId. Use an authenticated protector backed by a key that is separate from object storage credentials. Never implement the protector as plaintext or reversible encoding.

Receipt stores must implement atomic lease acquisition and compare-and-swap updates. Core checkpoints phase: "sealing" before invoking record encryption. If a crash occurs after ciphertext storage, resume authenticates that ciphertext against the source before advancing. If encryption might have happened but no ciphertext is durable, SecureTransferResumeUnsafeError requires a new transfer and capability rather than risking nonce reuse. Receipt adapters should implement SecureTransferProtectedReceiptLifecycleStore; run sweepExpiredReceipts() with its returned cursor until truncated is false.

The descriptor contains the decryption capability and sensitive metadata. It is plaintext until the caller protects it with @absolutejs/secure-messaging or an E2EE envelope. Never place it in object metadata, logs, URLs, push payloads, or a normal chat message.

Security model

  • Storage receives opaque transfer IDs, record indexes, ciphertext sizes, and expiry. It does not receive filenames, media types, conversation IDs, or keys.
  • Every record is bound to the transfer, attachment, conversation, sender, position, total count, expected plaintext size, final-record marker, and expiry.
  • Record creation is create-only. A collision must never overwrite ciphertext.
  • Missing, reordered, substituted, duplicated, truncated, and extended records fail authentication or descriptor validation.
  • Downloads target a staging sink. commit() occurs only after every record is authenticated; failure calls abort() so partial plaintext is not mistaken for a complete file.
  • Range downloads preserve the same staging rule but authenticate only records intersecting the requested byte interval.
  • Revocation stores are trusted authorization state and should use credentials and retention controls distinct from untrusted ciphertext storage.
  • Upload failure makes a best-effort ciphertext cleanup. Production adapters should implement SecureTransferLifecycleStore and run bounded expiry sweeps repeatedly with the returned cursor until truncated is false so live records at the start of a listing cannot starve crash-orphan cleanup.

This framing is inspired by RFC 8188, especially its authenticated record sequence, truncation handling, and unique per-record nonce requirements. It is not the RFC 8188 HTTP wire format. The resumable state machine follows RFC 5116 section 3.1, which calls for durable nonce checkpointing before encryption proceeds.

File names and media types are untrusted display hints. Applications must enforce allowlists, size limits, safe download dispositions, and client-side inspection after decryption. Server-side malware scanning cannot inspect end-to-end encrypted ciphertext without deliberately changing the confidentiality boundary.

Scope

Version 0.2.0 provides upload, strict descriptor, receipt, and revocation encoding, full and byte-range authenticated download, resumable crash recovery, transactional sinks, honest future-fetch revocation, cleanup, and provider/store contracts. Concrete local and S3/R2 storage adapters live in secure-transfer-adapters. Version 0.3.0 adds epoch-bound fresh-capability replacement and staged supersession after MLS membership changes.

License

Apache-2.0