Skip to content

Releases: Ajayvarmaramineni/BYOC

v0.4.0 — streaming everywhere, direct browser upload, V3 encryption

Choose a tag to compare

@Ajayvarmaramineni Ajayvarmaramineni released this 06 Sep 00:14
b33ec4d

[0.4.0] - 2026-09-06

The release where a file no longer has to fit in memory, and where the bytes no
longer have to pass through your server at all.

Highlights

Every adapter streams, in both SDKs. S3, WebDAV, Google Drive, local and
in-memory all accept an async iterator and transfer it without buffering the
object. An 800 MB upload holds a flat ~57 MB of resident memory instead of
850 MB, and a file larger than RAM is now possible rather than impossible.
Migration pipes source to target directly, so moving objects between providers
is bounded by the chunk size rather than by the largest file.

Direct browser upload. createUploadGrant() mints a capability that is safe
to hand to an untrusted client, and @byoc/browser
consumes it. The bytes go from the user's machine to the user's own cloud; your
server sees a path and a size and never any content. S3 signs a PUT covering
only host and UNSIGNED-PAYLOAD; Google Drive opens a resumable session whose
URI is itself the capability and needs no Authorization header, keeping your
OAuth token server-side.

Framed BYOC_E2EE_V3 encryption. AES-GCM authenticates one message with one
tag, so encrypting a file used to mean holding all of it. V3 splits the payload
into independently authenticated frames, binding the header, frame index and a
final-frame marker as additional authenticated data so reordering, truncation
and header swaps are all detected. V1 and V2 envelopes remain readable.

Added

  • @byoc/browser: uploadWithGrant() with progress, retry, abort and resumable chunking
  • createUploadGrant() on both clients, gated on a new directUpload capability
  • encryptStream / decryptStream in both SDKs
  • Streaming upload for S3 (multipart), WebDAV (chunked) and Google Drive (resumable)
  • spec/fixtures/upload-grant.json, pinning the grant wire format across SDKs
  • Exact V3 envelope sizing via encryptedSize() / encrypted_size()
  • UploadOptions.contentLength for exact-length streaming requests; the
    encrypted wrapper converts plaintext length to the V3 envelope length
  • A deterministic V3 cross-SDK fixture plus hostile chunk-boundary, truncation,
    reorder, oversized-frame, empty-object and header-tampering tests
  • A repeatable bounded-memory benchmark via npm run benchmark:streaming
  • Static security scanning in CI: bandit and pip-audit

Fixed

  • S3 rejected unknown-length streams. A chunked PUT gets 411 Length Required, so such a body now goes up as a multipart upload. Each part is fully known when signed, so every part signs its real SHA-256 rather than UNSIGNED-PAYLOAD -- streaming no longer weakens request signing on that path.
  • Provider-returned XML could expand DTD entities. ListObjectsV2 and PROPFIND responses are attacker-influenceable and parsed on every list call; both parsers now refuse entity expansion, with tests asserting the refusal rather than trusting a library default.
  • A null optional field in a grant broke every upload from a Python backend to a JavaScript frontend, because size > null is true in JavaScript. Absent optional fields are now omitted rather than serialized as null.
  • A missing grant expiry was treated as "never expires." It now fails closed.
  • The Python client dropped a provider defining __len__ while it was empty, having registered by truthiness rather than is not None.
  • A token-storage test asserted a two-byte needle was absent from random ciphertext, which collides by chance and failed roughly one push in a hundred while looking like a Python-version bug.

Changed

  • EncryptedStorageWrapper streams true stream inputs, keeps buffered inputs
    payload-signed and retryable, reports plaintext sizes, and preserves the
    underlying provider's resumable capability
  • Pure cryptography interop tests no longer require a running S3 server
  • All packages and adapter manifests report 0.4.0
  • SECURITY.md records where streaming has landed per adapter and the measured memory figures

Verification

Google Drive is validated against a real account by
python/scripts/validate_gdrive_live.py, now including the streaming path:
20/20 checks passing, among them a stream of exactly one chunk, which is the
case the resumable lookahead exists for. Both SDKs stream the same 100 MB file
to identical SHA-256.

TypeScript   416 tests
Python       435 tests, plus 42 integration and 13 interop

Known limitations

  • @byoc/connect and the metadata index are not in this release. Both need design decisions -- token storage location, and what happens when a user edits a file directly in their cloud -- that are better made deliberately than quickly.
  • Two instances of the same provider type still cannot be registered on one client, since the registry is keyed by manifest id.
  • move and copy of a filename containing ? fail against wsgidav. BYOC sends a correctly encoded Destination; wsgidav decodes it and re-parses as a URL, truncating at the ?. Confirmed with curl against the server directly.

v0.3.0 — local and in-memory providers, complete operation surface

Choose a tag to compare

@Ajayvarmaramineni Ajayvarmaramineni released this 05 Sep 04:47
08cd88b

The release that makes BYOC runnable without an account, and closes a set of
operations that existed in one SDK but not the other.

Highlights

Two providers that need no credentials. @byoc/local and @byoc/memory on npm, LocalFileSystemProvider and MemoryProvider in the Python package. Both pass the same behavioural suite as the network-backed adapters and both certify clean against the provider compliance harness. Evaluating BYOC, developing offline, and unit-testing code that talks to storage no longer require signing up for anything.

copy() is reachable. It was implemented on all three TypeScript adapters in 0.2.x and exposed by neither client, so no caller could reach any of it. Both clients now expose it, and the Python S3 and Google Drive adapters have real implementations rather than a capability flag with nothing behind it.

A conformance fixture for the operation surface. The existing fixtures pin bytes, and an adapter missing a method still writes identical bytes for everything it does implement. spec/fixtures/provider-operations.json pins which operations and capabilities each adapter has, in both SDKs, so this class of divergence fails CI instead of shipping.

Added

  • @byoc/local / LocalFileSystemProvider: files on a local disk or mounted volume, with paths confined to the configured root
  • @byoc/memory / MemoryProvider: an in-memory test double with snapshot(), clear(), and a configurable stream chunk size
  • copy() on both clients, capability-gated like move()
  • walk() on both clients: every object beneath a path, descending into folders
  • delete_tree(): recursive delete, children before parents, failures collected rather than aborting
  • delete_many(): concurrent batch delete returning per-path outcomes, because a partial delete is the common real case
  • signed_url() / getSignedUrl() on both clients, gated on public_urls. It existed only on the S3 adapter before
  • copy() and move() on the Python S3 adapter, using x-amz-copy-source
  • copy() and move() on the Python Google Drive adapter, using files.copy and a parent-list PATCH
  • spec/fixtures/provider-operations.json, and the conformance suites that read it in both SDKs
  • Both new adapters accept an async iterator as upload input, making good on what StorageInput has always declared

Fixed

  • S3 server-side copy truncated the copy source at a # or ?. copyObject built the x-amz-copy-source header with encodeURI, which leaves both characters intact, so copying draft#2.pdf asked S3 for draft and reported the object missing. The header is now RFC 3986 encoded per segment, the same rule the object URL already followed. Verified against MinIO in both SDKs.
  • The Python client dropped a provider that defined __len__ while it was empty. Registration tested the adapter for truthiness rather than is not None, so an empty MemoryProvider failed to register and the client reported that no provider had been supplied.
  • S3CompatibleProvider and GoogleDriveProvider in Python declared server_side_copy=True with no copy method. Callers who feature-detected on the capability got an AttributeError instead of a clean CapabilityUnsupportedError.
  • The in-memory provider did not report nested keys as folders, unlike S3, which returns them as CommonPrefixes. Anything below the first level was invisible to a tree walk, so delete_tree() reported complete success while leaving those objects in place. Caught before release by running the two providers through one shared suite.

Changed

  • Adapter manifests report adapterVersion / adapter_version 0.3.0
  • The Python S3 adapter's presigned_url() is now signed_url(), matching what the client calls. The old name still works as an alias.
  • AuthType gained no new members: the "local" variant already existed and is now used

Known limitations

  • E2EE still buffers the whole payload, so client-side encryption and multi-gigabyte resumable uploads remain mutually exclusive. The design for a framed envelope is in docs/design/streaming-and-chunked-e2ee.md; the transport work is not in this release.
  • Streaming uploads are still buffered by the three network adapters. Only the local and in-memory providers consume an async iterator without collecting it first.
  • move and copy of a filename containing ? fail against wsgidav. BYOC sends a correctly percent-encoded Destination header; wsgidav decodes it and then re-parses the result as a URL, truncating at the ?. Confirmed with curl against the server directly, and reproducible in both SDKs. Other WebDAV servers may or may not share the defect.
  • The Python SDK is async only. A synchronous facade for Celery, Django, and scripts is planned.
  • Two instances of the same provider type cannot be registered on one client, since the registry is keyed by manifest id.
  • Google Drive is not covered by CI, as it needs a real account and a browser consent step.

Install

npm install @byoc/core @byoc/local      # try it with no credentials
npm install @byoc/core @byoc/s3-compatible

pip install byoc-storage

Full changelog: v0.2.1...v0.3.0

v0.2.1

Choose a tag to compare

@Ajayvarmaramineni Ajayvarmaramineni released this 27 Aug 05:02

What's in this release

  • Two peer SDKs. TypeScript on npm as @byoc/core plus three provider adapters; Python on PyPI as byoc-storage, imported as byoc.
  • Three providers. Google Drive (OAuth 2.0 with PKCE, drive.file scope only), S3-compatible (Cloudflare R2, AWS S3, MinIO, Wasabi), and WebDAV (Nextcloud, ownCloud, Synology).
  • Cross-SDK compatibility. Both SDKs run against shared conformance vectors, so a file written by a Next.js frontend is readable by a FastAPI backend, including client-side encrypted files.
  • Client-side E2EE. AES-256-GCM with PBKDF2 at 600,000 iterations and a versioned envelope that records its own work factor.
  • Verified against real servers. 193 TypeScript tests, 224 Python tests, and 13 cross-SDK interop tests, run against live MinIO, WebDAV, and Google Drive rather than mocks.

Fixed since 0.2.0

@byoc/core and @byoc/google-drive did not declare @types/node, even though their published .d.ts files reference Buffer and node:stream in the public API. Consumers compiling with skipLibCheck: false saw Cannot find name 'Buffer' reported from inside the package. Both now depend on @types/node, so it resolves automatically.

The Python SDK is unaffected and stays at 0.2.0.

Known limitations

  • E2EE buffers the whole payload, so encryption does not yet compose with multi-gigabyte resumable uploads
  • The Python SDK is async only; a synchronous facade is planned
  • Google Drive is validated manually rather than in CI, since it needs a real account and a browser consent step

Setup: Google Drive OAuth guide · Full changelog: CHANGELOG.md