Problem
The async status handler pulls an arbitrary resource type and ID out of the admin database, taken from a literal reference stored in the job's output[bundle]:
;; modules/rest-api/src/blaze/rest_api/async_status_handler.clj
"completed"
(if-let [[type id] (some-> job job-async/response-bundle-ref fsr/split-literal-ref)]
(do-sync [response-bundle (fhir-util/pull db type id)]
(ring/response response-bundle))
...)
type is whatever the reference happens to say. Nothing checks that it is Bundle, so the handler is, in principle, a read primitive for any resource in the admin database — including the DocumentReference holding the page ID cipher key set.
Today this is not exploitable, but only by construction:
- the only writer of that reference is
blaze.job.async-interaction/add-response-bundle-reference, which hardcodes (str "Bundle/" id),
POST /__admin/Task cannot be used to plant a crafted reference — the job validator resolves output[bundle] against the AsyncInteractionResponseBundle target profile and fails,
- there is no
PUT/PATCH on /__admin/Task/{id}, so the output cannot be set after creation,
- and the key set resource's LUID is not exposed anywhere.
So the invariant that keeps this safe lives in a different module from the code that depends on it, and every one of the four barriers above is incidental to this handler. __async-status/{id} is always enabled, unlike the Admin API, which makes it worth hardening on its own.
Proposal
Check the type before pulling, e.g. only follow the reference when type is Bundle and otherwise treat the job as having no response bundle (falling through to the existing job-util/response-resource branch) or return a fault. This makes the assumption local to the handler instead of depending on a distant writer.
Notes
Found while documenting blaze.page-id-cipher (#3951). Hardening only — no behaviour change for jobs written by Blaze itself, so the regression test should cover a job whose output[bundle] points at a non-Bundle resource.
Problem
The async status handler pulls an arbitrary resource type and ID out of the admin database, taken from a literal reference stored in the job's
output[bundle]:typeis whatever the reference happens to say. Nothing checks that it isBundle, so the handler is, in principle, a read primitive for any resource in the admin database — including theDocumentReferenceholding the page ID cipher key set.Today this is not exploitable, but only by construction:
blaze.job.async-interaction/add-response-bundle-reference, which hardcodes(str "Bundle/" id),POST /__admin/Taskcannot be used to plant a crafted reference — the job validator resolvesoutput[bundle]against theAsyncInteractionResponseBundletarget profile and fails,PUT/PATCHon/__admin/Task/{id}, so the output cannot be set after creation,So the invariant that keeps this safe lives in a different module from the code that depends on it, and every one of the four barriers above is incidental to this handler.
__async-status/{id}is always enabled, unlike the Admin API, which makes it worth hardening on its own.Proposal
Check the type before pulling, e.g. only follow the reference when
typeisBundleand otherwise treat the job as having no response bundle (falling through to the existingjob-util/response-resourcebranch) or return a fault. This makes the assumption local to the handler instead of depending on a distant writer.Notes
Found while documenting
blaze.page-id-cipher(#3951). Hardening only — no behaviour change for jobs written by Blaze itself, so the regression test should cover a job whoseoutput[bundle]points at a non-Bundleresource.