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
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.