Repository navigation
Releases: Ajayvarmaramineni/BYOC
Release list
v0.4.0 — streaming everywhere, direct browser upload, V3 encryption
[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 chunkingcreateUploadGrant()on both clients, gated on a newdirectUploadcapabilityencryptStream/decryptStreamin 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.contentLengthfor 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:
banditandpip-audit
Fixed
- S3 rejected unknown-length streams. A chunked
PUTgets411 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 thanUNSIGNED-PAYLOAD-- streaming no longer weakens request signing on that path. - Provider-returned XML could expand DTD entities.
ListObjectsV2andPROPFINDresponses 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
nulloptional field in a grant broke every upload from a Python backend to a JavaScript frontend, becausesize > nullis 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 thanis 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
EncryptedStorageWrapperstreams 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.mdrecords 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/connectand 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.
moveandcopyof a filename containing?fail against wsgidav. BYOC sends a correctly encodedDestination; wsgidav decodes it and re-parses as a URL, truncating at the?. Confirmed withcurlagainst the server directly.
v0.3.0 — local and in-memory providers, complete operation surface
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 withsnapshot(),clear(), and a configurable stream chunk sizecopy()on both clients, capability-gated likemove()walk()on both clients: every object beneath a path, descending into foldersdelete_tree(): recursive delete, children before parents, failures collected rather than abortingdelete_many(): concurrent batch delete returning per-path outcomes, because a partial delete is the common real casesigned_url()/getSignedUrl()on both clients, gated onpublic_urls. It existed only on the S3 adapter beforecopy()andmove()on the Python S3 adapter, usingx-amz-copy-sourcecopy()andmove()on the Python Google Drive adapter, usingfiles.copyand a parent-listPATCHspec/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
StorageInputhas always declared
Fixed
- S3 server-side copy truncated the copy source at a
#or?.copyObjectbuilt thex-amz-copy-sourceheader withencodeURI, which leaves both characters intact, so copyingdraft#2.pdfasked S3 fordraftand 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 thanis not None, so an emptyMemoryProviderfailed to register and the client reported that no provider had been supplied. S3CompatibleProviderandGoogleDriveProviderin Python declaredserver_side_copy=Truewith nocopymethod. Callers who feature-detected on the capability got anAttributeErrorinstead of a cleanCapabilityUnsupportedError.- 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, sodelete_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_version0.3.0 - The Python S3 adapter's
presigned_url()is nowsigned_url(), matching what the client calls. The old name still works as an alias. AuthTypegained 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.
moveandcopyof a filename containing?fail against wsgidav. BYOC sends a correctly percent-encodedDestinationheader; wsgidav decodes it and then re-parses the result as a URL, truncating at the?. Confirmed withcurlagainst 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-storageFull changelog: v0.2.1...v0.3.0
v0.2.1
What's in this release
- Two peer SDKs. TypeScript on npm as
@byoc/coreplus three provider adapters; Python on PyPI asbyoc-storage, imported asbyoc. - Three providers. Google Drive (OAuth 2.0 with PKCE,
drive.filescope 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