Problem/Motivation
The module stores its state in two base fields on menu links, menu_autopilot (a map) and menu_autopilot_dynamic, both marked internal. Internal keeps them out of API responses. It does not keep API clients from writing them.
JSON:API filters internal fields when it normalizes an entity, but its resource type still lists them as enabled, and the denormalizer only skips fields that are disabled. The module defines no field access for the two fields, so a client that may update a menu link may also send menu_autopilot in a PATCH. The client can never read the field back, so the write leaves no visible trace.
What a write can do:
- Mark any link as a managed child of a node, so the next sync renames, moves or prunes it.
- Turn a link into a dynamic parent with a source of its choosing. With the replace policy that deletes the parent's other children on the next sync.
- Set or clear the flag that lets a later sync re-enable a link, including on a link an editor disabled on purpose.
The account needs update access to menu links, which is already a strong permission. The difference is that these writes skip the validation the form applies to a source descriptor, and they cause deletes on sites where the same account is not allowed to delete menu links.
Steps to reproduce
- As an account with update access to a menu link, PATCH the link over JSON:API with a
menu_autopilotattribute. - The request succeeds and the map is stored. A GET does not show it.
Found by reading 1.x and core's JSON:API denormalizer. Not yet reproduced on a site.
Proposed resolution
- Implement field access for both fields and forbid edit for every account. The module writes the fields from its own code, which field access does not affect. API layers and form widgets then refuse the write.
- Keep view forbidden as well, so the rule does not depend on the internal flag alone.
- Add a functional test that PATCHes the field over JSON:API and expects a refusal, and one that proves the module's own form and sync still write it.
Remaining tasks
- Check REST and GraphQL the same way.
- Note in the README that the fields are module-owned and not writable over any API.
API changes
An API write to either field, which used to succeed without effect on the response, is refused.
Comments
Comment #2
jmcerdaCommitted to 1.x; ships in 1.4.0. Reproduced before the fix on Drupal 11.4 with an account that holds only administer menu: a JSON:API PATCH of
menu_autopilotreturned 200 and stored the map, including a source descriptor with the replace policy. Core REST accepted the same write, and POST accepted it on create. A PATCH ofmenu_autopilot_dynamicreturned 200 and was not kept, because presave recomputes it. The module now forbids every field operation on both fields for every account. Its own form, sync and Drush paths write the fields from code and are unaffected. Forbidding view also closed a second gap found while testing: a JSON:API filter or sort onmenu_autopilot_dynamiclisted the dynamic parents; it now returns 403. A map that an API client wrote before this release is not detected or reverted.