Security
Smol API security overview
How the Smol API handles your files, how the engine that opens them is contained, what we record, and what we cannot yet show you.
1. In short
- A file sent in a direct request is processed and returned. It is not written to our file storage or to any database.
- A file sent as a job is stored only while it is needed: the input until the job ends, the output until it expires. A timer owned by the job deletes them.
- The processing engine runs in containers with no outbound internet access.
- Our usage records hold sizes, kinds and timings. They hold no file names and no file contents.
- The service reads every file in the clear in order to process it. It is not end-to-end encrypted.
- We have no SOC 2 report, no external penetration test and no uptime history yet. See section 12.
2. How files are handled
Direct requests
2.1A direct request carries a file of up to 25 MB to /v1/compress, /v1/convert or /v1/strip-metadata. The gateway passes the file to an engine and streams the result back in the same response, marked Cache-Control: no-store.
2.2Nothing is written to file storage or to a database except the usage record in section 9. Two temporary copies exist while the request runs: the gateway may hold a small file in memory so that it can retry a busy engine, and the engine saves a working copy in a private temporary directory on its own disk. That directory is deleted once the response has been sent, and also when the request fails or times out (after 120 seconds).
Jobs
2.3Larger files, and all video and office documents, are processed as jobs. The input is either uploaded through the API or fetched by us from an address you give. It is stored in Cloudflare R2 and deleted when the job finishes, whether it succeeds, fails or is canceled.
2.4The output is stored in R2 until it expires. The default is 1 hour after the job finishes. You can set it per account or per job, from 60 seconds to 24 hours. When the time comes, a timer owned by the job deletes the file.
2.5DELETE /v1/jobs/{id} cancels a job that is still running and deletes its input and output straight away.
2.6If you give an output address, the result is sent there directly and is not stored in R2 at all.
2.7As a second line of defence, 30 days after a job ends its record is erased together with any output still present. A clean-up that runs every hour deletes any upload that no job used, and the parts of any upload that was never finished, once they are 24 hours old.
What is held, where, and for how long
| Item | Where | Kept until |
|---|---|---|
| File in a direct request | Gateway memory; engine temporary directory | The response has been sent, or the request has failed |
| Job input | Cloudflare R2 | The job finishes, fails or is canceled |
| Job output | Cloudflare R2 (or sent straight to your storage) | It expires (1 hour by default; 60 seconds to 24 hours), or you delete the job |
| Engine working copies for a job | Engine temporary directory | The gateway has collected the result; at most 1 hour after the engine finishes if it is never collected |
| Upload never attached to a job | Cloudflare R2 | It is 24 hours old (a clean-up runs every hour) |
| Job record: file name, options, your metadata, input and output addresses, output headers, result figures, SHA-256 hashes, webhook deliveries | Cloudflare Durable Object storage | 30 days after the job ends |
| Stored job response for an idempotency key (includes file name and metadata) | Cloudflare D1 | It is 24 hours old (a clean-up runs every hour) |
| Job index: job ID, operation, status, file kind, error code, times | Cloudflare D1 | 30 days after the job was created |
| Usage record for each request | Cloudflare D1 | 400 days. Daily totals are kept while the account exists |
3. Encryption, and what this is not
3.1Requests to api.smolmac.com are served over HTTPS: [TO BE COMPLETED: confirm that plain HTTP is refused and the minimum TLS version set at Cloudflare].
3.2Stored files and records sit on Cloudflare storage (R2, D1 and Durable Objects). Cloudflare’s documentation states that each of these encrypts data at rest with AES-256, automatically. Cloudflare holds the keys; we do not.
3.3This is not end-to-end encryption. To compress or convert a file, the engine has to read it in the clear. A job’s files are readable by the service while they are stored, and so, technically, by the people who operate it. We do not look at customers’ files.
3.4If a file must never be readable by a third party, do not send it to any hosted service, including this one. The Smol Mac app does the same compression on your own machine.
4. The processing engine
The engine opens files from strangers with image, video, PDF and document tools. We treat every file as hostile and build the engine so that a file which breaks a tool has little to reach.
- No network. Engine containers have outbound internet access switched off. They can be reached only by the gateway, and they hold no keys, credentials or database access.
- Separate pools. Direct requests and long jobs run on separate pools of containers.
- Unprivileged, one private directory per request. The engine runs as a user without administrator rights. Each request gets its own temporary directory, deleted when the request is done.
- Type from contents, not from name. The engine works out what a file is from its leading bytes. The working copy is named after the detected type, never after the name the caller gave.
- Tools on a short leash. Each tool runs with an emptied environment, in the request’s directory, and is stopped when the time limit is reached, together with anything it started.
- Allow-listed image formats. The image library’s policy denies every format, then allows a named list: common image formats for reading and writing, camera raw formats for reading only, PDF for writing only. It cannot run other programs, follow indirect file references or open pipes, and it has memory, disk and size ceilings of its own.
- Limits. The engine refuses work above fixed ceilings: 100 megapixels per image or video frame, 2,000 frames in an animation, 2,000 pages in a PDF and 3 hours of media. Plans set lower limits and a file size limit. A direct request must finish within 120 seconds and a job within 4 hours.
- Pinned tools. The versions of the tools in the engine image are fixed, so behaviour changes only when we release a new image.
This is containment, not proof. The engine has not yet been tested by an outside party; see section 12.
5. API keys and sign-in
5.1A key is smol_live_ or smol_test_ followed by 32 random characters (about 190 bits). The fixed start lets secret scanners recognise a leaked key.
5.2We store a SHA-256 hash of each key, plus its first and last four characters so that you can tell keys apart. The key itself is shown once, when it is created, and cannot be recovered. A copy of our database would not give anyone a usable key.
5.3Revoking a key in the dashboard takes effect within 30 seconds.
5.4A key can be limited to a list of IP addresses or ranges when it is created. A request with that key from anywhere else is refused, so a key copied off your server is useless elsewhere.
5.5Keys are for servers. The API grants no cross-origin (CORS) permission, so it cannot be called from a web page.
5.6Test keys run the real engine, but only on the sample files we publish, which are recognised by their SHA-256 hash. They cost nothing and cannot process your own files.
5.7A job can be read, canceled or deleted only with a key of the account that created it, and live and test keys cannot see each other’s jobs.
5.8The dashboard uses the smolmac.com sign-in, which sends a one-time code by email. Session tokens are stored as hashes. The dashboard’s write requests are accepted only from our own pages.
5.9Every account has rate limits, limits on work running at once and a monthly spend cap, which bound the cost of a leaked key.
6. Webhooks, download links and receipts
Webhooks
6.1Each delivery carries Smol-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of <t>.<raw body> under your endpoint’s secret. Check the signature and reject timestamps more than five minutes old.
6.2Each endpoint has its own secret, shown once when the endpoint is created. Deliveries to a webhook address set on a single job are signed with a secret that belongs to the account. We have to keep webhook secrets in a readable form, because we need them to sign.
6.3A delivery that does not get a 2xx answer within 10 seconds is retried, up to eight attempts over roughly 20 hours. Redirects are not followed. The Smol-Event-Id header lets you ignore repeats.
Download links
6.4A finished job gives you a download link signed with HMAC-SHA256. It stops working when the result expires. Anyone who has the link can fetch the file until then, so treat it as a secret, or fetch the result with your API key instead.
Receipts
6.5For a finished job, GET /v1/jobs/{id}/receipt returns a statement of what happened: the job and operation, the SHA-256 hashes and sizes of the input and output, when the job was created and completed, when the input and the output were deleted, and whether the output went to your own storage.
6.6The statement is signed with an Ed25519 key and names the key used. The public keys for checking receipts are published at https://api.smolmac.com/v1/receipt_keys, which needs no API key; a key that has been replaced stays listed, so older receipts can still be checked. A receipt is available for 30 days after the job ends. Direct requests store nothing and have no receipt.
7. Addresses you give us
7.1A job can name an input address to fetch from, an output address to deliver to and a webhook address. Each must be a public https address on the standard port, with no user name or password in it. Loopback, private and internal names and addresses are refused, as are IPv6 literal addresses.
7.2When we fetch an input, each redirect is checked against the same rules before we follow it, up to three. The server must state the file’s length, and a file over your plan’s limit is refused. Output deliveries and webhooks do not follow redirects.
7.3Headers you supply for an output delivery are kept in the job record for 30 days. Use a short-lived signed upload address rather than a long-lived credential.
8. Where data is
8.1Everything runs on Cloudflare: the API on Workers, the engine on Containers, job files in R2, and records in D1 and Durable Objects. We run no servers of our own.
8.2We do not pin a region today. Cloudflare decides where a request is handled and where a file is stored. The company that operates Smol is based in the United States.
8.3Storage and processing limited to the European Union is planned. It is not available yet, and we will say so here when it is.
9. What we record
9.1Usage records. One row per request: request ID, account and key identifiers, live or test, time, operation, file kind, whether it succeeded, error code, what was charged, input and output sizes, and processing time. No file name and no file contents.
9.2Job records. The job record described in section 2 holds the file name and metadata you supplied, so that the job can be shown to you and your webhooks delivered. It is erased after 30 days.
9.3Operational logs. The gateway logs errors with a request or job ID and an error message. The engine logs, for each file, the operation, file kind, formats, sizes and time taken. When a tool fails, the engine logs which tool failed and its exit code. It does not log the tool’s error output, because that text can quote details from inside the file, such as a tag or a title. Files themselves are never logged, and the name you gave the file does not appear, because the engine’s working copy is not named after it.
9.4Platform logs. Cloudflare can record every request to a Worker automatically, address included. We have that turned off, because a request’s address can carry a file name (the filename parameter). What is logged is our own log lines only: request ids, error codes and timings. Cloudflare keeps those for 7 days. Cloudflare’s network still handles the usual network data for each request, such as IP address, under its own privacy policy.
9.5Account data. Email address, name, country, tax ID, card brand and last four digits, plan and payment status.
9.6These pages. The developer pages, the documentation, the dashboard and these legal and security pages load no advertising or analytics scripts, and send no page views to any advertising platform. The API privacy page says what is recorded when you arrive and what is reported when you sign up or pay.
10. Sub-processors
- Cloudflare. Hosting and processing: runs the API (Workers), the processing engine (Containers), temporary file storage for jobs (R2), and the account, job and usage records (D1 and Durable Objects). Handles your files.
- Stripe. Payments: stores the card, charges it, issues invoices, retries failed payments and calculates tax. Never receives your files.
- Resend. Transactional email: sign-in codes and account, billing and service notices. Never receives your files.
Card details are entered with Stripe and held by Stripe; we see only the card brand and last four digits. The full list, and how we announce changes, is on the sub-processors page.
11. Reporting a vulnerability
11.1Email [email protected] with “Security” in the subject line. A person reads it. Say what you found, how to reproduce it and what you think the effect is.
11.2Please test only against your own account, do not read or change other customers’ data, do not degrade the service, and give us a reasonable time to fix the problem before you publish it.
11.3We will not take legal action against research done in good faith within those limits. We do not offer paid rewards at present.
11.4To report abuse of the service rather than a weakness in it, see the Acceptable Use Policy.
12. What we do not have yet
- SOC 2. We have no SOC 2 report and no other independent certification.
- An external penetration test. None has been carried out yet.
- A public uptime history. The service is in preview, and no production uptime has been measured yet. The Uptime Agreement is not in force until it has.
- A European Union region. Planned, not available.
We will update this list when any of it changes, and not before.