Metadata-Version: 2.1
Name: SenaiteHotfix20260602
Version: 1.0.0
Summary: SENAITE security hotfix 2026-06-02 (JSON API RCE)
Home-page: https://www.senaite.com
Author: RIDING BYTES & NARALABS
Author-email: senaite@senaite.com
License: GPLv2
Description: =====================
        SenaiteHotfix20260602
        =====================
        
        Security hotfix for SENAITE LIMS, addressing a **critical** *unauthenticated
        remote code execution* in the SENAITE JSON API.
        
        This package backports the two upstream fixes (``senaite.core#2903`` and
        ``senaite.core#2919``) as runtime monkey patches so they can be installed on
        **any SENAITE.CORE 2.x release** (tested-stable from ``v2.0.0`` through
        ``v2.6.0``) without upgrading ``senaite.core`` itself. It follows the model of
        the Plone hotfixes (e.g. ``Products.PloneHotfix20210518``).
        
        
        What it fixes
        =============
        
        The vulnerability is a chain of two independent flaws. Both must be closed; the
        hotfix applies both:
        
        1. **Eval injection (CWE-95)** -- ``senaite.core#2903``.
           The JSON API parsed stringified ``RecordField`` / ``RecordsField`` payloads
           with the builtin ``eval()``, executing attacker-controlled Python in the
           Zope worker. The hotfix shadows the module-global ``eval`` in the three
           affected modules
        
           - ``bika.lims.jsonapi`` (``set_fields_from_request`` -- the RCE sink)
           - ``senaite.core.browser.fields.record`` (``RecordField.set``)
           - ``senaite.core.browser.fields.records`` (``RecordsField._to_dict``)
        
           with a safe parser based on ``ast.literal_eval``. This is behaviourally
           equivalent to the merged ``parse_record_literal`` helper.
        
        2. **Missing authorization (CWE-862)** -- ``senaite.core#2919``.
           The state-changing JSON API routes did not enforce the ``AccessJSONAPI``
           permission, so anonymous and under-privileged callers could reach them. The
           hotfix wraps every state-changing route method
        
           - ``update``, ``update_many``
           - ``remove``
           - ``doActionFor``, ``doActionFor_many``
           - ``getusers``
        
           so the ``AccessJSONAPI`` permission is checked before any mutation.
        
        
        How it works
        ============
        
        The patches are applied as an import side-effect of the ``SenaiteHotfix20260602``
        package. The package ships a ``z3c.autoinclude`` plugin (``target = plone``), so
        it is imported automatically at instance start-up -- the same mechanism that
        loads any SENAITE add-on. **Adding the egg and restarting the instance is all
        that is required**; there is no GenericSetup profile to install, no ZODB
        migration, and nothing to configure.
        
        The patches are idempotent and version-aware: on a ``senaite.core`` release that
        already carries the upstream fixes they are a harmless no-op (the eval sinks are
        gone, and the permission check simply runs a second, redundant time).
        
        
        Installation
        ============
        
        Add the egg to your instance and restart.
        
        Buildout
        --------
        
        .. code-block:: ini
        
            [instance]
            eggs +=
                SenaiteHotfix20260602
        
        If your deployment disables ``z3c.autoinclude`` auto-discovery, also load the
        ZCML explicitly:
        
        .. code-block:: ini
        
            [instance]
            eggs +=
                SenaiteHotfix20260602
            zcml +=
                SenaiteHotfix20260602
        
        pip
        ---
        
        .. code-block:: console
        
            $ pip install SenaiteHotfix20260602
        
        Then restart the instance.
        
        
        Verifying the fix
        =================
        
        After restart, the instance log shows::
        
            SenaiteHotfix20260602 installed (recordparsing, jsonapi_auth)
        
        An anonymous call to a gated route (the advisory's PoC) now returns a JSON
        error instead of executing code:
        
        .. code-block:: console
        
            $ curl -s "http://localhost:8080/senaite/@@API/update?obj_uid=<uid>&RejectionReasons=__import__('os')..."
            {"success": false, "error": true, ... "Unauthorized" ...}
        
        
        Testing
        =======
        
        The hotfix is tested against **every senaite.core release from v2.0.0 to
        v2.6.0** by the ``tests`` GitHub Actions workflow
        (``.github/workflows/tests.yml``). The ``2.x`` development branch is not tested
        because it already carries ``#2903`` and ``#2919`` (the hotfix is a no-op
        there). Each matrix cell:
        
        1. checks out that ``senaite.core`` tag,
        2. builds it with the tag's own ``buildout.cfg`` (for the ``develop = .``
           checkout under test and its structure), with the hotfix developed on top via
           ``test-senaite.cfg``,
        3. runs ``bin/test -s SenaiteHotfix20260602``.
        
        The dependency stack is pinned to what each release actually shipped with --
        the versions ``senaite.lims`` records, since ``senaite.core`` does not pin its
        own siblings:
        
        - **Siblings** (``senaite.app.listing`` / ``spotlight`` / ``supermodel``,
          ``senaite.impress``, ``senaite.jsonapi``) are **not** taken from their moving
          ``2.x`` git branch. They are installed as released eggs pinned to the versions
          the matching ``senaite.lims`` release pins (contemporaneous with the core tag;
          they track the core tag except for ``v2.4.1``, whose ``senaite.lims`` pins the
          siblings at ``2.4.0``).
        - **Plone** is re-pinned (via ``plone-kgs.cfg``) to the ``Plone==5.2.x`` that the
          matching ``senaite.lims`` release requires, because a tag's own ``buildout.cfg``
          sometimes extends a slightly different Plone point release that would clash
          with the ``senaite.lims`` egg.
        - A few **Python 2.7 fixup pins** (``magnitude``, ``Pympler``, ``et-xmlfile``)
          are applied in ``test-senaite.cfg``. These transitive dependencies are
          unpinned upstream and their newest releases dropped Python 2.7, so a
          from-scratch build of an old release today would otherwise pull a version that
          no longer builds. The values match the current ``senaite.core`` 2.x buildout.
        
        The suite has two parts:
        
        - ``tests/test_recordparsing.py`` -- version-independent unit tests of the safe
          literal parser (parses records, rejects code-execution payloads). Needs no
          SENAITE environment.
        - ``tests/test_jsonapi_auth.py`` -- asserts the patches actually land on the
          installed ``senaite.core``: ``eval`` is shadowed in all three modules, every
          state-changing route is gated, and the permission helper denies unauthorized
          callers.
        
        Running it locally
        ------------------
        
        One matrix cell can be reproduced locally (requires a Python 2.7 interpreter, a
        C toolchain and the ``libxml2`` / ``libxslt`` headers)::
        
            scripts/run-tests-local.sh v2.6.0
        
        To test against a senaite.core checkout you already have, copy the hotfix into
        it as ``SenaiteHotfix20260602/`` and ``test-senaite.cfg`` alongside, drop in the
        matching Plone known-good-set, then build with the sibling pins for that release
        (see the table the workflow uses) and run the suite::
        
            printf '[buildout]\nextends = https://dist.plone.org/release/5.2.15/versions.cfg\n' > plone-kgs.cfg
            bin/buildout -c test-senaite.cfg \
                versions:senaite.lims=2.6.0 \
                versions:senaite.app.listing=2.6.0 \
                versions:senaite.app.spotlight=2.6.0 \
                versions:senaite.app.supermodel=2.6.0 \
                versions:senaite.impress=2.6.0 \
                versions:senaite.jsonapi=2.6.0
            bin/test -s SenaiteHotfix20260602
        
        The standalone parser tests need nothing but an interpreter::
        
            python -m unittest SenaiteHotfix20260602.tests.test_recordparsing
        
        
        Compatibility
        =============
        
        - SENAITE.CORE 2.0.0 -- 2.6.0 (the patched code paths are identical across this
          range). Newer releases that already include the fixes are supported as a
          no-op.
        - Python 2.7 (Plone 5.2 / Zope 4). The code is Python 3 compatible should
          SENAITE move to it.
        
        
        Credits
        =======
        
        Security: Fix unauthenticated remote code execution chain in the JSON API
        (GHSA-jrw6-7x4q-w25j, CVE-2026-54569). Replaces dynamic evaluation of
        ``RecordField`` inputs with safe literal parsing (`#2903`_), and enforces the
        ``AccessJSONAPI``
        permission on the state-changing JSON API routes ``update``, ``update_many``,
        ``remove``, ``doActionFor``, ``doActionFor_many``, and ``getusers``
        (`#2919`_).
        
        Reported by Simon Weber, Volker Schönefeld and Chiara Fliegner, all of
        `Machine Spirits UG`_ (see their `advisory`_). Independently reported by Jérémy
        Luyé-tanet of `Synacktiv`_. Thanks for the responsible disclosure.
        
        The upstream fixes bundled here were authored by Tyler Coatsworth (`#2903`_) and
        Ramon Bartl (`#2919`_).
        
        .. _Machine Spirits UG: https://www.machinespirits.com
        .. _advisory: https://www.machinespirits.com/advisory/3b114a/
        .. _Synacktiv: https://www.synacktiv.com
        .. _#2903: https://github.com/senaite/senaite.core/pull/2903
        .. _#2919: https://github.com/senaite/senaite.core/pull/2919
        
        
        License
        =======
        
        GNU General Public License, version 2 (see ``docs/LICENSE.txt``).
        
        Changelog
        =========
        
        1.0.0 (2026-06-02)
        ------------------
        
        - Initial release. Bundles the fixes for a critical unauthenticated remote
          code execution in the SENAITE JSON API:
        
          - senaite.core#2903: replace ``eval()`` with safe literal parsing for
            ``RecordField`` / ``RecordsField`` payloads (CWE-95).
          - senaite.core#2919: enforce the ``AccessJSONAPI`` permission on the
            state-changing JSON API routes (CWE-862).
        
Keywords: senaite lims security hotfix patch jsonapi eval
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Web Environment
Classifier: Framework :: Plone
Classifier: Framework :: Zope2
Classifier: Framework :: Zope :: 4
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2.7
Classifier: Topic :: Security
Description-Content-Type: text/x-rst
