AbsoluteJS

Secure Transfer

@absolutejs/secure-transferv0.3.0betaPlatform & Infra

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

#Installation

BASH
bun add @absolutejs/secure-transfer

#Capabilities

Overview

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.

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.

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:

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:

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:

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.

Show 1 more

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,

Show 10 more

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.

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.

Outcomes

What you can build

Overview

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.

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.

Honest revocation

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

Hardening checklist

Production guidance

Honest revocationConfigure a trusted SecureTransferRevocationStore, then create the durable tombstone before attempting ciphertext cleanup:
Security modelStorage receives opaque transfer IDs, record indexes, ciphertext sizes, and

Follow in order

Troubleshooting path

1
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.

#@absolutejs/secure-transfer quick start

Partial snippet

# @absolutejs/secure-transfer

TS
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

Partial snippet

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.

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

#Honest revocation

Partial snippet

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

TS
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,
});

#Public entry points

Supported entry points declared by this project’s package manifest. Internal dist paths are not part of the package contract.

Public package entry point declared in package.json.

@absolutejs/secure-transfer@absolutejs/secure-transfer/manifest@absolutejs/secure-transfer/manifest.json

#Package commands

Scripts declared by this project’s package manifest.

bun run buildrm -rf dist && bun build src/index.ts src/manifest.ts --outdir dist --root src --sourcemap --target=browser --external @absolutejs/manifest --external @sinclair/typebox && tsc --project tsconfig.build.json && absolute-manifest emit
bun run check:packagebun run format:check && bun run typecheck && bun run test && bun run build && bun run verify-package && absolute-changelog check
bun run formatprettier --write "./**/*.{ts,json,md}"
bun run format:checkprettier --check "./**/*.{ts,json,md}"
bun run testbun test tests/
bun run typechecktsc --noEmit

#API reference

Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.

52 symbols
createSecureTransferClientvaluePermalink
TS
const createSecureTransferClient: (options: SecureTransferClientOptions) => SecureTransferClient;
Exported from @absolutejs/secure-transfer