PST-078: The editable-cell POST accepts the whole company form's fields beside the one cell #807

Closed
opened 2026-10-10 10:56:47 +00:00 by tiagoagueda · 1 comment
Owner

Found in the security audit of 2026-10-10 (SECURITY-AUDIT-2026-10-10.md), run against main at c30851b from 8 to 10 October 2026, as PST-078. Severity: Informational.

The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it.


  • Severity: Informational
  • Category: authorization-contract drift (own data only)
  • Location: src/postulo/core/cells.py (140-153)
  • Found by: ownership review of jobs

Who can do it, and what they need. The owner of the row only; no cross-account effect. Verified with test_more.py::test_cell_editor_accepts_declared_fields.

How it works.

  1. POST /jobs/companies/<own pk>/cell/name/ with name=Renamed and new_industries=Sneaked in (optionally location_correction=..., known_service=...).
  2. modelform_factory narrows Meta.fields to ['name'] but keeps every field CompanyForm declares; alone=True, which drops them, is not passed.
  3. CompanyForm.save() runs _apply_location_correction and _add_new_industries, so the 'one cell' edit also creates industries and moves the map pin. Measured: cell answered 200, company renamed and an industry 'Sneaked in' was attached.

Impact. No security boundary is crossed: everything written belongs to the requester and every queryset is still for_user. It bypasses the cell view's column_for gate ('this column is editable is decided in one place and cannot be worked around by typing an address') and lets a one-field endpoint write several fields, which is the kind of second save path CLAUDE.md warns about.

Verification (verdict: confirmed; severity after verification: Informational). Three corrections to the stated effect. (1) The logo fields are parsed but never acted on: apply_logo() is called by the page views, not by EditableCellView.post (cells.py:217-221 calls only form.save()), so a logo_url in the cell POST fetches nothing and changes nothing. (2) industries is accepted by the narrowed form but _save_m2m skips it (outside Meta.fields=['name']); only new_industries reaches the row. (3) known_service with a blank name renames the company to the registry name ('Workforce Australia') but leaves kind and website untouched because they are outside the narrowed fields, i.e. a half-applied record in the person's own data. …

Suggested remediation. Have EditableCellView.form_for pass alone=True (or have the cell form strip declared fields other than the column's), and add a test that a cell POST carrying new_industries / location_correction changes only the named field.

Found in the security audit of 2026-10-10 (`SECURITY-AUDIT-2026-10-10.md`), run against `main` at `c30851b` from 8 to 10 October 2026, as **PST-078**. Severity: **Informational**. The text below is the audit's entry, unchanged apart from links to the other issues it names. The finding was produced by automated review and checked by a second automated pass; re-check it before acting on it. --- - **Severity:** Informational - **Category:** authorization-contract drift (own data only) - **Location:** `src/postulo/core/cells.py` (140-153) - **Found by:** ownership review of jobs **Who can do it, and what they need.** The owner of the row only; no cross-account effect. Verified with test_more.py::test_cell_editor_accepts_declared_fields. **How it works.** 1. POST /jobs/companies/&lt;own pk>/cell/name/ with name=Renamed and new_industries=Sneaked in (optionally location_correction=..., known_service=...). 2. modelform_factory narrows Meta.fields to ['name'] but keeps every field CompanyForm declares; `alone=True`, which drops them, is not passed. 3. CompanyForm.save() runs _apply_location_correction and _add_new_industries, so the 'one cell' edit also creates industries and moves the map pin. Measured: cell answered 200, company renamed and an industry 'Sneaked in' was attached. **Impact.** No security boundary is crossed: everything written belongs to the requester and every queryset is still for_user. It bypasses the cell view's `column_for` gate ('this column is editable is decided in one place and cannot be worked around by typing an address') and lets a one-field endpoint write several fields, which is the kind of second save path CLAUDE.md warns about. **Verification** (verdict: confirmed; severity after verification: Informational). Three corrections to the stated effect. (1) The logo fields are parsed but never acted on: `apply_logo()` is called by the page views, not by `EditableCellView.post` (cells.py:217-221 calls only `form.save()`), so a `logo_url` in the cell POST fetches nothing and changes nothing. (2) `industries` is accepted by the narrowed form but `_save_m2m` skips it (outside Meta.fields=['name']); only `new_industries` reaches the row. (3) `known_service` with a blank name renames the company to the registry name ('Workforce Australia') but leaves `kind` and `website` untouched because they are outside the narrowed fields, i.e. a half-applied record in the person's own data. … **Suggested remediation.** Have EditableCellView.form_for pass alone=True (or have the cell form strip declared fields other than the column's), and add a test that a cell POST carrying new_industries / location_correction changes only the named field.
tiagoagueda added this to the 1.0.0 milestone 2026-10-10 10:58:11 +00:00
Author
Owner

Fixed in cd09e7839 on main.

Fixed in cd09e7839 on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#807
No description provided.