Quay mirror-fetch and VuFind handler-termination authority boundaries¶
Two August 6 feed records expose a reusable operator pattern: an early control appears to approve or deny a request, but a later worker or controller retains more authority than that decision represents.
- Red Hat Quay repository-level mirror configuration accepts
external_referenceand can pass it to a Skopeo-backed mirror worker without the URL validation used by the organization-level mirror handlers: CVE-2026-15927, Red Hat Bugzilla 2501256, and GHSA-9rpm-rwwc-xw43. - VuFind's controller-level access check can produce an access-denied response without stopping the requested controller function from executing: CVE-2026-52466 and GHSA-4m6p-2r8v-vvqp.
The GitHub records were unreviewed mirrors when this page was written. Red Hat's primary record confirms the Quay repository-mirror URL-validation boundary. Treat the VuFind item as a validation seed until the exact route, affected revision, and corrected control flow are confirmed in upstream source; do not invent affected or fixed version ranges.
Owned peers and denied side-effect sinks only
Use a disposable Quay deployment, an owned no-content registry recorder, synthetic repositories, a disposable VuFind instance, synthetic users and records, and patched controller side-effect sinks. Never point mirror configuration at metadata, loopback, private production services, or an unowned host. Never create, alter, export, email, or delete real VuFind data.
1. Map policy parity across Quay mirror route families¶
Start from authenticated route metadata or the exact deployed source revision. Inventory repository- and organization-level mirror handlers independently:
| Dimension | Capture |
|---|---|
| caller | authenticated principal and repository/organization role |
| route | method, normalized path, handler, object scope |
| input | raw external_reference, parsed scheme, authority, path |
| validation | validator name, allow/deny result, resolved addresses |
| handoff | queued task type and normalized registry reference |
| worker | Skopeo argv/config or patched connector arguments |
| network | redirect hop, DNS answer, final socket peer, denied decision |
Do not assume that equivalent UI forms share equivalent server controls. Compare create and update methods, repository and organization scopes, API and UI-backed handlers, and immediate validation versus asynchronous worker execution.
A useful parity matrix is:
| Case | Repository mirror | Organization mirror | Expected invariant |
|---|---|---|---|
| ordinary owned registry | accepted | accepted | same canonical authority reaches the worker |
| malformed authority | rejected | rejected | no job queued |
| userinfo / alternate port | same policy | same policy | parser cannot hide the final authority |
| redirect to owned denied peer | denied before connect | denied before connect | every hop is revalidated |
| owned DNS name with changed answer | denied at final peer | denied at final peer | validation and connection cannot diverge |
Use only two owned listeners: an ordinary registry-shaped control and an isolated denied canary. Return no credentials, manifests, or image layers. Instrument the connector so it records the final authority tuple and refuses the socket.
A bounded positive is:
repository administrator sets synthetic external_reference
-> repository handler omits or disagrees with URL validation
-> mirror job is queued
-> patched worker selects the owned denied peer
-> equivalent organization handler rejects before queueing
This proves route-family validation drift and worker-side outbound authority without contacting an internal service. An accepted URL alone is insufficient; preserve the queue handoff and final-peer decision.
2. Test the asynchronous final-peer boundary¶
Quay's worker handoff matters because request-time parsing, queue serialization, Skopeo normalization, DNS, redirects, and the final connection may each see a different authority. Record the value at every transition:
raw API field
-> parsed URL/registry reference
-> validated authority and DNS set
-> queued representation
-> worker/Skopeo input
-> redirect authority
-> final DNS answer and socket peer
Exercise harmless canonicalization classes: hostname case and trailing dot, userinfo, explicit default ports, IPv4/IPv6 textual forms, redirects between the two owned peers, and controlled DNS answer changes. Do not test real private, link-local, loopback, or metadata addresses.
Report separate claims:
- validator parity failure: the repository route accepts a canary rejected by the organization route;
- queue drift: the queued reference differs materially from the validated reference;
- final-peer failure: the patched connector would select an owned peer outside the approved policy; and
- response relay: only if synthetic response metadata is actually returned to the caller.
Do not infer cloud credential access or internal-service reachability from missing request-time validation alone.
3. Verify that VuFind denial terminates dispatch¶
For VuFind, derive affected controllers and actions from the exact source revision or upstream patch. Do not brute-force route names. Build a route matrix with one synthetic object and patched sinks:
| Credential state | Controller policy | Response | Handler entry | Side-effect sink |
|---|---|---|---|---|
| none | denied | access denied | must not occur | must not occur |
| authenticated wrong role | denied | access denied | must not occur | must not occur |
| intended role | allowed | normal result | occurs | recorder only |
| fixed build, wrong role | denied | access denied | must not occur | must not occur |
Place recorders at four points: validateAccessPermission, the selected action's first line, object resolution, and every read/export/mutation/message sink. A denial response is not proof of safety if handler execution continues after the response object is created.
Use action classes rather than destructive examples:
- read/detail rendering of a synthetic marker;
- export/download selection with a fake record ID;
- create/update/delete routed to a no-op transaction recorder;
- email, webhook, or job scheduling routed to a denied queue; and
- batch or alternate-format siblings, if present in source.
A bounded positive is:
unauthorized synthetic request
-> access validator records DENY
-> response is marked access denied
-> controller action entry recorder still fires
-> patched sink receives only the synthetic object ID
-> fixed build returns before action entry
This proves non-terminating authorization control flow. It does not prove disclosure or mutation unless the corresponding sink is reached with an object outside the caller's authority.
4. Separate response semantics from server-side effects¶
Preserve both the client-visible response and server-side trace. Frameworks may discard a later handler response, roll back a transaction, or suppress rendering while still performing reads, logging, queueing, or other side effects.
For each action, capture:
- policy decision and reason;
- response object/status created by the validator;
- whether dispatch continues;
- resolved synthetic object and owner;
- transaction state;
- denied sink invocation;
- final client-visible status/body hash; and
- affected-versus-fixed control-flow difference.
Use precise outcomes:
| Evidence | Supported claim |
|---|---|
| denial response plus action entry | authorization guard fails to terminate dispatch |
| foreign synthetic object reaches read recorder | object disclosure path, with data access denied |
| no-op mutation recorder receives foreign ID | unauthorized mutation path, without mutation |
| queue recorder receives synthetic job | unauthorized asynchronous side-effect path |
| only generic error timing differs | insufficient; investigate before reporting |
Evidence bundle¶
Product and exact revision/build:
Caller role and object scope:
Route family / method / handler:
Raw-to-canonical URL or request trace:
Policy decision:
Queue or dispatch continuation:
Final-peer or object identity:
Patched sink result:
Affected-build result:
Fixed-build result:
Supported claim:
Explicitly excluded stronger claims:
Keep registry credentials, image metadata, user records, search history, email addresses, and session material out of evidence. Hash synthetic bodies where content is unnecessary.