Opened 53 minutes ago
Last modified 49 minutes ago
#37213 new Bug
`ForeignKeyRawIdWidget` renders labels for related objects outside the form field's queryset
| Reported by: | Natalia Bidart | Owned by: | |
|---|---|---|---|
| Component: | contrib.admin | Version: | 6.0 |
| Severity: | Normal | Keywords: | ForeignKeyRawIdWidget formfield_for_foreignkey not-security |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The Security Team received this report and concluded this is worth a public hardening:
ForeignKeyRawIdWidget.label_and_url_for_value() resolves the submitted value through the related model's default manager:
self.rel.model._default_manager.using(db).get(**{key: value})
This ignores the queryset assigned to the bound form field. When a ModelAdmin narrows a raw-id foreign key via formfield_for_foreignkey() (for example, restricting the queryset per request), the widget still renders the __str__() label and admin change link for any valid primary key, including values outside that narrowed queryset.
The admin add view seeds initial form data from request.GET, so a staff user with add permission on the referencing model can pass an arbitrary related-object primary key in the query string and see the rendered label and change URL, even for objects the field's queryset would reject on submit. Form validation still rejects the value on POST, and the related model's admin views still enforce their own permissions (the change view returns 403), so this is limited to the label and URL rendered at GET time.
The widget's display path should be consistent with the field's queryset: resolve the value through the bound field's queryset rather than the model's default manager, and omit the label and link when the value falls outside it (matching the existing behavior for a missing primary key).
Thanks Jaeyoung Jang for the report.
To reproduce, using these from the admin tests:
Reproduction, given a staff user Alex with only
add_cartire, owning Car #1, and Robin owning Car #2: