Skip to content
Developer

S3-compatible endpoint

Point rclone, aws-cli, mc, Cyberduck or any backup tool at your workspace's objects — the protocol every storage tool already speaks.

backlex has object storage, a REST API for it, signed URLs and resumable uploads. What it did not have is the one protocol every existing tool already speaks.

Terminal window
backlex s3 create --name "backups"
access key id BLXK7Q2M4PXR9TCVW
secret access key 9f2c… (shown once)
mode read-write
prefix (whole workspace)
Terminal window
aws --endpoint-url https://your-instance/s3 \
s3 sync ./local s3://your-workspace/backups/

rclone, aws-cli, mc, Cyberduck, restic, duplicity — anything that talks S3 works, with no adapter on either side.

The bucket is the workspace

One bucket per workspace, named after the workspace slug. A SigV4 request carries no workspace header, so the credential names the workspace; the bucket in the URL is checked against it and never used to choose one. Changing the URL cannot reach another workspace’s objects.

It is a second protocol, not a second store

Every object goes through the same storage adapter the REST API uses, and every key through the same validation. An object written by rclone appears in the admin’s file browser immediately, is served by /api/storage/<key>, counts toward the workspace’s storage usage, and is included in backups. A key the REST API would refuse is refused here identically, with the S3 error code for it.

Objects written over S3 are private. A protocol client has no way to say “publish this”, and defaulting to public would make an S3 upload quietly more exposed than the same upload through the API.

Credentials

OptionWhat it does
--prefix team-a/The credential can only see and write keys under that prefix — in both directions, including listings. A request for a broader prefix is narrowed, never widened.
--read-onlyEvery mutating verb is refused. What a backup tool should hold.
--disableStops working on the next request. There is no session to expire.

The secret is stored, not hashed — and why

Every other credential in backlex is stored as a digest. This one cannot be. SigV4 is not a bearer scheme: the client derives a signing key from the secret and signs the request with it, so the server has to derive the same key to check the signature. A hash cannot do that; this is the trade AWS itself makes.

The stored copy is encrypted with the deployment’s AUTH_SECRET, which is real protection against a database dump and none at all against someone who already has the application’s environment. Treat an S3 credential as more sensitive than an API key, scope it with --prefix and --read-only, and give it an expiry.

The secret is returned once, by create. There is no read-back endpoint — adding one would undo the encryption for anyone who reaches the admin API.

What is implemented

Chosen by what the tools above actually call:

OperationNotes
GET / HEAD / PUT / DELETE object
ListObjectsV2prefix, delimiter, max-keys, continuation-token
DeleteObjects (POST ?delete)Per-key failures are reported in the body, as S3 does
ListBucketsReturns the one bucket
Multipart: create / upload part / complete / abortRequires a backend that can assemble parts; otherwise NotImplemented

Everything else — versioning, ACLs, bucket policies, lifecycle, replication, tagging, CORS configuration, website hosting — returns a proper S3 NotImplemented error rather than a 404. Each is a whole subsystem whose absence a tool copes with, where a wrong answer would make it misbehave.

ListParts answers with an empty list rather than a 501: every client that calls it does so to resume, and an empty list means “start over”, which every one of them handles. A 501 would abort the upload.

Authentication

Both AWS SigV4 shapes are accepted: the Authorization header (what almost everything sends) and presigned query parameters (what a shared link is). There is no signature-version-2 fallback — a verifier that accepts a weaker scheme is exactly as strong as the weaker scheme.

Checked on every request: the signature itself (in constant time), a ±15 minute clock skew so a captured request is not replayable forever, a presigned URL’s own X-Amz-Expires, and that every header the signature claims to cover was actually sent.

Chunked uploads (Content-Encoding: aws-chunked, which mc and aws s3 cp use for large files) are accepted. The seed signature — the one over the headers — is verified exactly as for any other request; the per-chunk signatures are not. AWS’s own STREAMING-UNSIGNED-PAYLOAD-TRAILER does not sign chunks either, the request is already authenticated by the seed, and the body’s integrity in transit is TLS’s job.

A single signed PUT is capped at 64 MB, because the signature has to be checked against the whole body before any of it is stored. Larger objects go through multipart, which is what every client does anyway.

Surfaces

SurfaceWhere
S3 protocol/s3
REST/api/admin/s3-credentials
SDKbacklex.s3.*
MCPs3.list_credentials, s3.create_credential, s3.update_credential, s3.delete_credential
CLIbacklex s3 …