On this page

A security page is worth reading only if it lists what went wrong. This one lists all of it: 24 weaknesses found in the service's own code, each with its fix and, where one was recorded, its test, plus what was searched for and not found. Which means: you can check the rating and the evidence yourself.

The severity is a self-rating. This is not an external audit and not a penetration test. Nobody outside has tested the service.

The worst case: one customer reads another's documents

Searched, not found, at the storage level

A job belongs to the pair of API key and job number. A second key cannot read it, finish it, or learn anything about it.

What the test checks

A test checks that one key cannot read another key's job.

Not found does not mean it cannot happen.

Tests: TestJobsAreIsolatedByKey, TestStatusRevealsNothingAboutOthers, TestIdemKeyIsUniquePerKeyHolderAndFindable.

Found and fixed: 24 weaknesses

0rated critical.
1rated high.
10rated medium.
13rated low.

Self-rated. The hash is the short name of the fix in the version history. That the test failed before the fix is confirmed for F04 to F08, F13 and F17. For the others it was not recorded, so it is not claimed. Where there is no test, the table says so.

Class No Severity What could have happened Fix and test
Dependencies F01 high The Go runtime had reachable flaws, among them an overload attack on HTTP/2. The service could have been knocked over from outside. 5f0885f8, Go 1.26.2 to 1.26.10. Vulnerability check in the release check.
Dependencies F02 medium A reachable flaw in an image-format decoder: a crafted TIFF could have tied up service time. 5f0885f8, library updated. Vulnerability check.
Dependencies F03 medium Outdated image and Python libraries. The image library went from 60 to 41 families of reports. 5f0885f8. Vulnerability check.
Input F05 low The worker read any image format, and PNG without a pixel limit. A pixel bomb would have reached it; the service catches such files earlier. fb03c07b. test_worker_refuses_pixel_bombs_and_foreign_image_formats
Input F07 low Three offline scripts opened images without a list of allowed formats. fb03c07b. test_pillow_formats.py
Input F13 medium One hostile page in the middle of a large job ended the whole job. Now only that page fails. b96c2f94. renderfail_test.go
Resources F06 low A client that announced a length and then went silent held a worker thread. Reachable only on the machine itself. fb03c07b. test_worker_drops_a_stalled_upload
Resources F08 low The free test could take more reading places than promised, so paying customers could have waited. 45c4d54b. TestTryReadingPlacesFollowMaxShare
Resources F14 medium Checking new uploads shared places with rendering and waited without limit. In the test the small job of a second key waited 2.5 to 5.4 seconds; in production it could wait without end. 8b69ad64, 3b5dfcd6. TestUploadWaitForInspectionIsBounded, TestBigUploadDoesNotSlowOtherRequests, TestRenderSlotsGoRoundRobinBetweenKeys
Resources F15 medium One open document held a work place, and the uploads and refusals of others waited behind it. bc032d9e. TestRunningJobDoesNotBlockUploadsAndRefusals
Resources F24 low On a graphics card limited to 3 GiB, large pages (A4 at 300 dpi, for example) ended in an error. The memory a page may ask for is now bounded; pages that fitted before read exactly as before. fb73a207, e2d4062c. test_det_pixel_bound_caps_the_input_area_and_keeps_boxes_in_the_page_raster, test_plan_rec_cell_budget_caps_wide_calls
Configuration F04 low Answers lacked the nosniff header, so a browser could have read a JSON answer as another kind of content. fb03c07b. TestEveryAnswerSetsNosniff
Configuration F09 low The idle time of the API server was not set explicitly. 45c4d54b. TestAPIServerSetsEveryTimeout
Configuration F12 low Private buckets were allowed without an error message, and bucket keys containing .. were not skipped. 1df63420. TestPrivateBucketsLogAnErrorAtStart, TestKeysWithDotDotSegmentAreSkipped
Configuration F16 medium An old configuration holding a withdrawn key stopped the service from starting after an update. 73413888, 23d9c89a. The configuration is checked before every restart.
Configuration F23 low A route that creates a key outside any order was on by default. It already needed a signed admin request. It is now off unless switched on for tests. 131be944. TestAdminKeyRouteIsOffByDefault
Install and update F17 medium During an update the worker answered briefly with 501, and a page ended as failed instead of waiting. 8e7f0343. TestUnsupportedDuringRolloutLeavesPagePending
Install and update F18 low An update flag dated in the future kept the service waiting. 6ec65de9. TestFutureUpdateFlagIsIgnored
Install and update F19 low A script swallowed a failed restart, so the way back came too late. 6ec65de9. No test.
Install and update F20 medium The update script of the test instance deleted the whole test directory when a path ended in a slash. Test instance only. 6ec65de9, a path guard. No test.
Access F10 medium Requests without an Authorization header counted toward the 15-minute lock of an address. Third parties behind the same address could have locked customers out. 1df63420. TestMissingHeaderDoesNotCountTowardLockout
Access F11 low The address lock also hit valid keys from the same address. 3e097eeb. No named test.
Access F22 medium Switching off a primary key did not switch off the sub-keys made from it. Mail domains given as IP addresses were accepted. A blanked client address could stay in freed database space. f0c9cb13. TestDeactivatePrimaryAlsoSwitchesOffSubKeys, TestIPLiteralDomainIsNotAnAddress, TestBlankedAddressLeavesNoTraceInTheDatabaseFile
Data retention F21 low The table of jobs grew without limit. Finished jobs are now removed after 90 days. 73413888. TestPurgeDoneKeepsRunningAndYoungJobs

Searched for, with nothing found

Only what a test or a check carries is listed.

Searched Result Evidence
Another customer's documents or results Not found at the storage level The tests in the first section above.
Getting around the API key Not found A live check made as a customer: without a key the answer is 401, and a key without ordering rights gets 404 on the ordering routes. TestPrefixGuessingDoesNotLockOwner, TestOrderUsageOwnerOnlyAndChecksParameters, TestRequestAnswerDoesNotRevealKnownAddresses
Code run by an uploaded file Not found A hostile-document test set: decompression bombs, ZIP, gzip, encrypted files, JavaScript, launch actions. Live: JavaScript, launch and link actions in PDFs are not run, a 20000 by 20000 pixel image is answered with page_too_large, a 5000-page bomb is contained, junk is answered with invalid_document, and the type is decided by content, not by header. Flaws in the libraries used remain possible.
Reaching internal addresses through bucket jobs Not found Bucket tests (CheckSource, the guarded connection guardedDialer). The switch that allows local addresses is not in the production build.
Order form: quantity field, reuse of the link Not found TestLinkIsSingleUseAndExpires. Unknown fields are refused. The package is fixed.
Upload files left on disk Not found for temporary upload files (only these) TestSpoolRemovedOnEveryError, TestSweepRemovesStaleSpoolFiles, TestTIFFTimeoutLeavesNoSpoolFile, TestTryWorkerFailuresDeleteThePage, TestDeleteRunningJobCancelsIt
Customer webhooks as an attack surface There are none The source was read. A test is not needed for something that does not exist.

What stays on disk, and when billing stops

The document is deleted when the job ends. The result stays for 24 hours by default (result_ttl) and is then removed; DELETE /v1/jobs/{id} removes it at once. There is no end-to-end test that proves nothing is left, so that is not claimed.

When a job budget, a balance or the free limit runs out, the remaining pages are not read and not billed. Tests: TestJobBudgetStopsAndRestIsNotRun, TestEmptyBalanceStopsWithNotRun, TestFreeLimitStopsUnpaidNotPaid.

The workers listen on the local address only (127.0.0.1 is their default).

Production use, high volume or your own server?

Private use and tests are free. For a company in production, a server of your own or a pipeline built on your pages, send the kind of page and the volume, and get a written offer, or a clear no when the fit is not there.

Ask for an offer