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.
backlex s3 create --name "backups"access key id BLXK7Q2M4PXR9TCVWsecret access key 9f2c… (shown once)mode read-writeprefix (whole workspace)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
| Option | What 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-only | Every mutating verb is refused. What a backup tool should hold. |
--disable | Stops 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:
| Operation | Notes |
|---|---|
GET / HEAD / PUT / DELETE object | |
ListObjectsV2 | prefix, delimiter, max-keys, continuation-token |
DeleteObjects (POST ?delete) | Per-key failures are reported in the body, as S3 does |
ListBuckets | Returns the one bucket |
| Multipart: create / upload part / complete / abort | Requires 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
| Surface | Where |
|---|---|
| S3 protocol | /s3 |
| REST | /api/admin/s3-credentials |
| SDK | backlex.s3.* |
| MCP | s3.list_credentials, s3.create_credential, s3.update_credential, s3.delete_credential |
| CLI | backlex s3 … |