Security
hybrids3 is meant to sit on a private network or behind a TLS proxy, holding files for services and agents you control. This page lists what it does to protect them, where it falls short, and what to set before anyone else can reach it.
Before exposing it
- Replace every key from
config.example.yaml. They are published. - Bind the port to
127.0.0.1or keep it on an internal Docker network, and put a TLS proxy in front for remote clients. hybrids3 itself speaks plain HTTP, so keys in Bearer headers and MCPauth_keyarguments travel in the clear without TLS. - Treat every
public: truebucket as readable by the whole network. - Give clients bucket keys. Keep the master key for administration, or leave
master_keyempty. - Pick bucket names other than
docs,redocandopenapi.json, which FastAPI serves itself. - Protect
config.yamllike a password file. It holds every key in plain text.
What is protected
- Signature checks. S3 requests and presigned URLs are verified with AWS Signature V4. The
public_keyonly identifies the bucket; the signature has to match the private key. - Constant-time comparison. Every key comparison uses
hmac.compare_digest. - Keys stay on the server. No response or presigned URL contains a private key, and hybrids3 never logs one. MCP
list_bucketsreturns names and settings only. - Hidden buckets. Unauthorized access gets the same
404as a missing bucket. See Keys and access for the exceptions. - Path traversal. Every key is resolved against its bucket directory and refused with
400if it would land outside. - Prefix filters.
%,_and\in a listingprefixare escaped before they reach SQLite’sLIKE. - Atomic writes and streaming. Uploads go to a temporary file and are renamed into place. Bodies stream to disk instead of memory, and
max_file_sizestops them early. - Bounded waiting. Per-object locks have a queue limit and timeouts, so a flood on one key gets
503instead of piling up. - MCP limits. Tool errors hide exception details, and downloads over 50 MB are refused.
- Non-root container. The process runs as UID 1000 and writes only
/data. X-Content-Type-Options: nosniffon every response, so browsers do not guess a different type for stored files.
Known gaps
These follow from the code as it is today:
- No replay window for signed requests. hybrids3 does not compare
X-Amz-Datewith the clock on header-signed requests. A captured signed request can be sent again later. - Body checksums are not verified. The
x-amz-content-sha256value is part of the signature, but nothing checks it against the body. Someone who captures a signed PUT could resend it with a different body. TLS prevents both this and the replay above. - Client-signed presigned URLs are not capped. URLs that hybrids3 generates expire within 7 days. A URL signed by a client holding the key is trusted for whatever
X-Amz-Expiresit carries, and one withoutX-Amz-Expiresnever expires. - No revocation. A leaked key or presigned URL stays valid until you rotate that key in the configuration and restart.
- Bucket names can be probed.
GET /<bucket>?locationanswers200for any configured bucket without a key. - The MCP connection token is optional and grants nothing. Requests without a token pass, and a valid token does not limit the tools. With
path_prefixset, wrong tokens are not rejected either. Per-toolauth_keychecks are what protect the data. - The access log prints query strings. uvicorn logs every request line, query string included. An MCP token sent as
?auth=<key>and the signatures of presigned URLs end up indocker logs. Prefer theAuthorizationheader for MCP clients that can send one. - No rate limiting. Wrong keys can be tried as fast as the server answers. Put rate limits in the proxy.
- FastAPI’s API pages are public.
/docs,/redocand/openapi.jsonanswer without a key and describe the HTTP routes. - No encryption at rest and no CORS headers. Use disk encryption on the host, and add CORS at the proxy if browsers need it.
What is stored
| Data | Where |
|---|---|
| Keys | config.yaml, plain text, never written anywhere else |
| Object bytes | /data/<bucket>/<key>, unencrypted |
| Object metadata | /data/hybrids3.db: key names, sizes, content types, MD5 ETags and timestamps |
| Logs | stdout: request IDs, bucket names, object keys, sizes and error codes, plus uvicorn’s access log with every request line. No object content |
Object keys are logged, so do not put secrets in key names.
Reporting a problem
Open an issue on GitHub. Pull requests are closed automatically.