java-topology/whitepaper/outreach/tryton.md
russell@unturf.com 6784cdf1cf feat: add 39 outreach docs (batches 6-8)
Batch 6 (9): dolibarr, jitsi-videobridge, zed, tryton, suricata,
  strawberry, zulip, zesarux, zephyr
Batch 7 (15): xonotic (4), xash3d (3), xenia, xtuple, zabbix (2),
  zathura, zebra, yabause, zephyr-0001
Batch 8 (15): woodpecker (2), wine (4), widelands (3), wesnoth (3),
  wekan (3)

Mix of CWE-407 and CWE-312.
2026-04-14 17:06:28 -04:00

3.5 KiB

Tryton ERP — CWE-407 Disclosure Brief

2026-04-13 · Patches available — awaiting upstream merge

Finding

Two O(n^2) defects in Tryton's ORM layer across ModelStorage._save_values() and ModelView._changed_values(). Both patched. Patches ready for upstream review. Both defects fire during record save operations on One2Many and Many2Many relational fields.

The Defects

tryton-0001a (PATCHED — MEDIUM): trytond/trytond/model/modelstorage.py:2115

# list comprehension + list.remove() — O(T^2) for relational field saves
previous = [t.id for t in getattr(self, fname)]  # list
# ...
if target.id in previous:       # O(T) linear scan per target
    previous.remove(target.id)  # O(T) list removal per target

previous holds IDs of existing related records as a list. For each target record, in previous performs a linear scan and previous.remove() performs another linear scan plus element shift. With T related records, total cost: O(T^2).

tryton-0001b (PATCHED — MEDIUM): trytond/trytond/model/modelview.py:922

# Identical pattern in _changed_values()
previous = [t.id for t in init_targets if t.id]  # list
# ...
if target.id in previous:       # O(T) linear scan
    previous.remove(target.id)  # O(T) list removal

Same pattern in the change-detection path. Fires on every form save when the client computes which relational fields changed.

Complexity Proof

tryton-0001a/b: At T=500 related records:

  • Defective: 500 * 500 = 250,000 comparisons + 250,000 element shifts
  • Fixed: 500 set lookups + 500 set discards
  • 500x op reduction at T=500.

Impact

Tryton serves as a full ERP framework used by businesses for invoicing, inventory, accounting, and project management. The _save_values() method fires on every record save involving One2Many or Many2Many fields. The _changed_values() method fires on every form submission to detect field modifications.

Business scenarios with large relational fields (invoice lines, stock moves, project tasks) regularly reach hundreds of related records. An invoice with 200 line items triggers ~40,000 extra comparisons on every save.

The Fix

tryton-0001a: Replace list with set for the previous collection:

# Before
previous = [t.id for t in getattr(self, fname)]
if target.id in previous:
    previous.remove(target.id)

# After
previous = set(t.id for t in getattr(self, fname))
if target.id in previous:
    previous.discard(target.id)

tryton-0001b: Same pattern in _changed_values():

# Before
previous = [t.id for t in init_targets if t.id]

# After
previous = set(t.id for t in init_targets if t.id)

Patch

Fixes available:

  • defects/tryton-0001/patch/tryton-0001-modelstorage-save-values.patch
  • defects/tryton-0001/patch/tryton-0001-modelview-changed-values.patch

Two-file patch across modelstorage.py and modelview.py. Both sites: list to set conversion for O(1) membership and O(1) discard. 500x op reduction at T=500 related records.

What We Ask

Patches ready for review.

  1. Confirm receipt and assign an issue reference (tryton/trytond on Heptapod or bugs.tryton.org).
  2. Assess severity — both defects fire on every relational-field save in the ORM.
  3. Coordinate a disclosure date — we target 90 days from first contact.
  4. We will credit the Tryton team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.